golang-jwt is a Go implementation of JSON Web Tokens. Unclear documentation of the error behavior in `ParseWithClaims` can lead to situation where users are potentially not checking errors in the way they should be. Especially, if a token is both expired and invalid, the errors returned by `ParseWithClaims` return both error codes. If users only check for the `jwt.ErrTokenExpired ` using `error.Is`, they will ignore the embedded `jwt.ErrTokenSignatureInvalid` and thus potentially accept invalid tokens. A fix has been back-ported with the error handling logic from the `v5` branch to the `v4` branch. In this logic, the `ParseWithClaims` function will immediately return in "dangerous" situations (e.g., an invalid signature), limiting the combined errors only to situations where the signature is valid, but further validation failed (e.g., if the signature is valid, but is expired AND has the wrong audience). This fix is part of the 4.5.1 release. We are aware that this changes the behaviour of an established function and is not 100 % backwards compatible, so updating to 4.5.1 might break your code. In case you cannot update to 4.5.0, please make sure that you are properly checking for all errors ("dangerous" ones first), so that you are not running in the case detailed above.

Improper Handling of Exceptional Conditions

The product does not handle or incorrectly handles an exceptional condition.

func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
	token, parts, err := p.ParseUnverified(tokenString, claims)
	if err != nil {
		return token, err
	}

	// Verify signing method is in the required set
	if p.ValidMethods != nil {
		var signingMethodValid = false
		var alg = token.Method.Alg()
		for _, m := range p.ValidMethods {
			if m == alg {
				signingMethodValid = true
				break
			}
		}
		if !signingMethodValid {
			// signing method is not in the listed set
			return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
		}
	}

	// Lookup key
	var key interface{}
	if keyFunc == nil {
		// keyFunc was not provided.  short circuiting validation
		return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
	}
	if key, err = keyFunc(token); err != nil {
		// keyFunc returned an error
		if ve, ok := err.(*ValidationError); ok {
			return token, ve
		}
		return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
	}

	vErr := &ValidationError{}

	// Validate Claims
	if !p.SkipClaimsValidation {
		if err := token.Claims.Valid(); err != nil {

			// If the Claims Valid returned an error, check if it is a validation error,
			// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
			if e, ok := err.(*ValidationError); !ok {
				vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
			} else {
				vErr = e
			}
		}
	}

	// Perform validation
	token.Signature = parts[2]
	if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
		vErr.Inner = err
		vErr.Errors |= ValidationErrorSignatureInvalid
	}

	if vErr.valid() {
		token.Valid = true
		return token, nil
	}

	return token, vErr
}

func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
	token, parts, err := p.ParseUnverified(tokenString, claims)
	if err != nil {
		return token, err
	}

	// Verify signing method is in the required set
	if p.ValidMethods != nil {
		var signingMethodValid = false
		var alg = token.Method.Alg()
		for _, m := range p.ValidMethods {
			if m == alg {
				signingMethodValid = true
				break
			}
		}
		if !signingMethodValid {
			// signing method is not in the listed set
			return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
		}
	}

	// Lookup key
	var key interface{}
	if keyFunc == nil {
		// keyFunc was not provided.  short circuiting validation
		return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
	}
	if key, err = keyFunc(token); err != nil {
		// keyFunc returned an error
		if ve, ok := err.(*ValidationError); ok {
			return token, ve
		}
		return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
	}

	// Perform validation
	token.Signature = parts[2]
	if err := token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
		return token, &ValidationError{Inner: err, Errors: ValidationErrorSignatureInvalid}
	}

	vErr := &ValidationError{}

	// Validate Claims
	if !p.SkipClaimsValidation {
		if err := token.Claims.Valid(); err != nil {
			// If the Claims Valid returned an error, check if it is a validation error,
			// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
			if e, ok := err.(*ValidationError); !ok {
				vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
			} else {
				vErr = e
			}
			return token, vErr
		}
	}

	// No errors so far, token is valid.
	token.Valid = true

	return token, nil
}

--- a/parser.go
+++ b/parser.go
@@ -34,12 +34,17 @@
 		return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
 	}

+	// Perform validation
+	token.Signature = parts[2]
+	if err := token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
+		return token, &ValidationError{Inner: err, Errors: ValidationErrorSignatureInvalid}
+	}
+
 	vErr := &ValidationError{}

 	// Validate Claims
 	if !p.SkipClaimsValidation {
 		if err := token.Claims.Valid(); err != nil {
-
 			// If the Claims Valid returned an error, check if it is a validation error,
 			// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
 			if e, ok := err.(*ValidationError); !ok {
@@ -47,20 +52,12 @@
 			} else {
 				vErr = e
 			}
+			return token, vErr
 		}
 	}

-	// Perform validation
-	token.Signature = parts[2]
-	if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
-		vErr.Inner = err
-		vErr.Errors |= ValidationErrorSignatureInvalid
-	}
+	// No errors so far, token is valid.
+	token.Valid = true

-	if vErr.valid() {
-		token.Valid = true
-		return token, nil
-	}
-
-	return token, vErr
+	return token, nil
 }


The verify function in the Stark Bank Python ECDSA library (aka starkbank-escada or ecdsa-python) before 2.0.1 fails to check that the signature is non-zero, which allows attackers to forge signatures on arbitrary messages.

Improper Verification of Cryptographic Signature

The product does not verify, or incorrectly verifies, the cryptographic signature for data.

    def verify(cls, message, signature, publicKey, hashfunc=sha256):
        byteMessage = hashfunc(toBytes(message)).digest()
        numberMessage = numberFromByteString(byteMessage)
        curve = publicKey.curve
        r = signature.r
        s = signature.s
        inv = Math.inv(s, curve.N)
        u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
        u2 = Math.multiply(publicKey.point, n=(r * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
        add = Math.add(u1, u2, A=curve.A, P=curve.P)
        modX = add.x % curve.N
        return r == modX

    def verify(cls, message, signature, publicKey, hashfunc=sha256):
        byteMessage = hashfunc(toBytes(message)).digest()
        numberMessage = numberFromByteString(byteMessage)
        curve = publicKey.curve
        r = signature.r
        s = signature.s
        if not 1 <= r <= curve.N - 1:
            return False
        if not 1 <= s <= curve.N - 1:
            return False
        inv = Math.inv(s, curve.N)
        u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
        u2 = Math.multiply(publicKey.point, n=(r * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
        add = Math.add(u1, u2, A=curve.A, P=curve.P)
        modX = add.x % curve.N
        return r == modX

--- a/ellipticcurve/ecdsa.py
+++ b/ellipticcurve/ecdsa.py
@@ -4,6 +4,10 @@
         curve = publicKey.curve
         r = signature.r
         s = signature.s
+        if not 1 <= r <= curve.N - 1:
+            return False
+        if not 1 <= s <= curve.N - 1:
+            return False
         inv = Math.inv(s, curve.N)
         u1 = Math.multiply(curve.G, n=(numberMessage * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)
         u2 = Math.multiply(publicKey.point, n=(r * inv) % curve.N, N=curve.N, A=curve.A, P=curve.P)


capsule-proxy is a reverse proxy for Capsule Operator which provides multi-tenancy in Kubernetes. In versions prior to 0.2.1 an attacker with a proper authentication mechanism may use a malicious `Connection` header to start a privilege escalation attack towards the Kubernetes API Server. This vulnerability allows for an exploit of the `cluster-admin` Role bound to `capsule-proxy`. There are no known workarounds for this issue.

Improper Authentication

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

func (n kubeFilter) impersonateHandler(writer http.ResponseWriter, request *http.Request) {
	hr := req.NewHTTP(request, n.usernameClaimField, n.client)

	var username string

	var groups []string

	var err error

	if username, groups, err = hr.GetUserAndGroups(); err != nil {
		serverr.HandleError(writer, err, "cannot retrieve user and group")
	}

	n.log.V(4).Info("impersonating for the current request", "username", username, "groups", groups)

	if len(n.bearerToken) > 0 {
		request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", n.bearerToken))
	}

	request.Header.Add("Impersonate-User", username)

	for _, group := range groups {
		request.Header.Add("Impersonate-Group", group)
	}
}

func (n kubeFilter) impersonateHandler(writer http.ResponseWriter, request *http.Request) {
	hr := req.NewHTTP(request, n.usernameClaimField, n.client)

	var username string

	var groups []string

	var err error

	if username, groups, err = hr.GetUserAndGroups(); err != nil {
		serverr.HandleError(writer, err, "cannot retrieve user and group")
	}

	n.log.V(4).Info("impersonating for the current request", "username", username, "groups", groups)

	if len(n.bearerToken) > 0 {
		request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", n.bearerToken))
	}
	// Dropping malicious header connection
	// https://github.com/clastix/capsule-proxy/issues/188
	n.removingHopByHopHeaders(request)

	request.Header.Add("Impersonate-User", username)

	for _, group := range groups {
		request.Header.Add("Impersonate-Group", group)
	}
}

func (n *kubeFilter) removingHopByHopHeaders(request *http.Request) {
	connectionHeaderName, upgradeHeaderName, requestUpgradeType := "connection", "upgrade", ""

	if httpguts.HeaderValuesContainsToken(request.Header[connectionHeaderName], upgradeHeaderName) {
		requestUpgradeType = request.Header.Get(upgradeHeaderName)
	}
	// Removing connection headers
	for _, f := range request.Header.Values(connectionHeaderName) {
		for _, sf := range strings.Split(f, ",") {
			if sf = textproto.TrimString(sf); sf != "" {
				request.Header.Del(sf)
			}
		}
	}

	if requestUpgradeType != "" {
		request.Header.Set(connectionHeaderName, upgradeHeaderName)
		request.Header.Set(upgradeHeaderName, requestUpgradeType)

		return
	}

	request.Header.Del(connectionHeaderName)
}

--- a/internal/webserver/webserver.go
+++ b/internal/webserver/webserver.go
@@ -16,6 +16,9 @@
 	if len(n.bearerToken) > 0 {
 		request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", n.bearerToken))
 	}
+	// Dropping malicious header connection
+	// https://github.com/clastix/capsule-proxy/issues/188
+	n.removingHopByHopHeaders(request)

 	request.Header.Add("Impersonate-User", username)

--- /dev/null
+++ b/internal/webserver/webserver.go
@@ -0,0 +1,24 @@
+func (n *kubeFilter) removingHopByHopHeaders(request *http.Request) {
+	connectionHeaderName, upgradeHeaderName, requestUpgradeType := "connection", "upgrade", ""
+
+	if httpguts.HeaderValuesContainsToken(request.Header[connectionHeaderName], upgradeHeaderName) {
+		requestUpgradeType = request.Header.Get(upgradeHeaderName)
+	}
+	// Removing connection headers
+	for _, f := range request.Header.Values(connectionHeaderName) {
+		for _, sf := range strings.Split(f, ",") {
+			if sf = textproto.TrimString(sf); sf != "" {
+				request.Header.Del(sf)
+			}
+		}
+	}
+
+	if requestUpgradeType != "" {
+		request.Header.Set(connectionHeaderName, upgradeHeaderName)
+		request.Header.Set(upgradeHeaderName, requestUpgradeType)
+
+		return
+	}
+
+	request.Header.Del(connectionHeaderName)
+}


The idayrus/evoting repository before 2022-05-08 on GitHub allows absolute path traversal because the Flask send_file function is used unsafely.

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.

def private_static(filename):
    # Get path
    filepath = path.join(app.config.get("PRIVATE_DIR"), filename)
    if path.isfile(filepath):
        return send_file(filepath)
    # End
    return abort(404)

def private_static(filename):
    # Get path
    filepath = safe_join(app.config.get("PRIVATE_DIR"), filename)
    if path.isfile(filepath):
        return send_file(filepath)
    # End
    return abort(404)

--- a/app/helper/middleware.py
+++ b/app/helper/middleware.py
@@ -1,6 +1,6 @@
 def private_static(filename):
     # Get path
-    filepath = path.join(app.config.get("PRIVATE_DIR"), filename)
+    filepath = safe_join(app.config.get("PRIVATE_DIR"), filename)
     if path.isfile(filepath):
         return send_file(filepath)
     # End


Nuclei is a vulnerability scanner powered by YAML based templates. Starting in version 3.0.0 and prior to version 3.3.2, a vulnerability in Nuclei's template signature verification system could allow an attacker to bypass the signature check and possibly execute malicious code via custom code template. The vulnerability is present in the template signature verification process, specifically in the `signer` package. The vulnerability stems from a discrepancy between how the signature verification process and the YAML parser handle newline characters, combined with the way multiple signatures are processed. This allows an attacker to inject malicious content into a template while maintaining a valid signature for the benign part of the template. CLI users are affected if they execute custom code templates from unverified sources. This includes templates authored by third parties or obtained from unverified repositories. SDK Users are affected if they are developers integrating Nuclei into their platforms, particularly if they permit the execution of custom code templates by end-users. The vulnerability is addressed in Nuclei v3.3.2. Users are strongly recommended to update to this version to mitigate the security risk. As an interim measure, users should refrain from using custom templates if unable to upgrade immediately. Only trusted, verified templates should be executed. Those who are unable to upgrade Nuclei should disable running custom code templates as a workaround.

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.

func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {
	// while re-signing template check if it has a code protocol
	// if it does then verify that it is signed by current signer
	// if not then return error
	if tmpl.HasCodeProtocol() {
		sig := GetSignatureFromData(data)
		arr := strings.SplitN(string(sig), ":", 3)
		if len(arr) == 2 {
			// signature has no fragment
			return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
		}
		if len(arr) == 3 {
			// signature has fragment verify if it is equal to current fragment
			fragment := t.GetUserFragment()
			if fragment != arr[2] {
				return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
			}
		}
	}

	buff := bytes.NewBuffer(RemoveSignatureFromData(data))
	// if file has any imports process them
	for _, file := range tmpl.GetFileImports() {
		bin, err := os.ReadFile(file)
		if err != nil {
			return "", err
		}
		buff.WriteRune('\n')
		buff.Write(bin)
	}
	signatureData, err := t.sign(buff.Bytes())
	if err != nil {
		return "", err
	}
	return signatureData, nil
}

func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {
	digestData := ReDigest.Find(data)
	if len(digestData) == 0 {
		return false, errors.New("digest not found")
	}

	digestData = bytes.TrimSpace(bytes.TrimPrefix(digestData, []byte(SignaturePattern)))
	// remove fragment from digest as it is used for re-signing purposes only
	digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
	digest, err := hex.DecodeString(digestString)
	if err != nil {
		return false, err
	}

	buff := bytes.NewBuffer(RemoveSignatureFromData(data))
	// if file has any imports process them
	for _, file := range tmpl.GetFileImports() {
		bin, err := os.ReadFile(file)
		if err != nil {
			return false, err
		}
		buff.WriteRune('\n')
		buff.Write(bin)
	}

	return t.verify(buff.Bytes(), digest)
}

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/md5"
	"crypto/rand"
	"crypto/sha256"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"

	"github.com/projectdiscovery/gologger"
	"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
	errorutil "github.com/projectdiscovery/utils/errors"
)

func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {
	existingSignature, content := ExtractSignatureAndContent(data)

	// while re-signing template check if it has a code protocol
	// if it does then verify that it is signed by current signer
	// if not then return error
	if tmpl.HasCodeProtocol() {
		if len(existingSignature) > 0 {
			arr := strings.SplitN(string(existingSignature), ":", 3)
			if len(arr) == 2 {
				// signature has no fragment
				return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
			}
			if len(arr) == 3 {
				// signature has fragment verify if it is equal to current fragment
				fragment := t.GetUserFragment()
				if fragment != arr[2] {
					return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
				}
			}
		}
	}

	buff := bytes.NewBuffer(content)
	// if file has any imports process them
	for _, file := range tmpl.GetFileImports() {
		bin, err := os.ReadFile(file)
		if err != nil {
			return "", err
		}
		buff.WriteRune('\n')
		buff.Write(bin)
	}
	signatureData, err := t.sign(buff.Bytes())
	if err != nil {
		return "", err
	}
	return signatureData, nil
}

func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {
	signature, content := ExtractSignatureAndContent(data)
	if len(signature) == 0 {
		return false, errors.New("no signature found")
	}

	if !bytes.HasPrefix(signature, []byte(SignaturePattern)) {
		return false, errors.New("signature must be at the end of the template")
	}

	digestData := bytes.TrimSpace(bytes.TrimPrefix(signature, []byte(SignaturePattern)))
	// remove fragment from digest as it is used for re-signing purposes only
	digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
	digest, err := hex.DecodeString(digestString)
	if err != nil {
		return false, err
	}

	buff := bytes.NewBuffer(content)
	// if file has any imports process them
	for _, file := range tmpl.GetFileImports() {
		bin, err := os.ReadFile(file)
		if err != nil {
			return false, err
		}
		buff.WriteRune('\n')
		buff.Write(bin)
	}

	return t.verify(buff.Bytes(), digest)
}

func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
	dataStr := string(data)
	if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
		signature = []byte(strings.TrimSpace(dataStr[idx:]))
		content = []byte(strings.TrimSpace(dataStr[:idx]))
	} else {
		content = data
	}
	return
}

--- a/pkg/templates/signer/tmpl_signer.go
+++ b/pkg/templates/signer/tmpl_signer.go
@@ -1,24 +1,27 @@
 func (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {
+	existingSignature, content := ExtractSignatureAndContent(data)
+
 	// while re-signing template check if it has a code protocol
 	// if it does then verify that it is signed by current signer
 	// if not then return error
 	if tmpl.HasCodeProtocol() {
-		sig := GetSignatureFromData(data)
-		arr := strings.SplitN(string(sig), ":", 3)
-		if len(arr) == 2 {
-			// signature has no fragment
-			return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
-		}
-		if len(arr) == 3 {
-			// signature has fragment verify if it is equal to current fragment
-			fragment := t.GetUserFragment()
-			if fragment != arr[2] {
+		if len(existingSignature) > 0 {
+			arr := strings.SplitN(string(existingSignature), ":", 3)
+			if len(arr) == 2 {
+				// signature has no fragment
 				return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
+			}
+			if len(arr) == 3 {
+				// signature has fragment verify if it is equal to current fragment
+				fragment := t.GetUserFragment()
+				if fragment != arr[2] {
+					return "", errorutil.NewWithTag("signer", "re-signing code templates are not allowed for security reasons.")
+				}
 			}
 		}
 	}

-	buff := bytes.NewBuffer(RemoveSignatureFromData(data))
+	buff := bytes.NewBuffer(content)
 	// if file has any imports process them
 	for _, file := range tmpl.GetFileImports() {
 		bin, err := os.ReadFile(file)

--- a/pkg/templates/signer/tmpl_signer.go
+++ b/pkg/templates/signer/tmpl_signer.go
@@ -1,10 +1,14 @@
 func (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {
-	digestData := ReDigest.Find(data)
-	if len(digestData) == 0 {
-		return false, errors.New("digest not found")
+	signature, content := ExtractSignatureAndContent(data)
+	if len(signature) == 0 {
+		return false, errors.New("no signature found")
 	}

-	digestData = bytes.TrimSpace(bytes.TrimPrefix(digestData, []byte(SignaturePattern)))
+	if !bytes.HasPrefix(signature, []byte(SignaturePattern)) {
+		return false, errors.New("signature must be at the end of the template")
+	}
+
+	digestData := bytes.TrimSpace(bytes.TrimPrefix(signature, []byte(SignaturePattern)))
 	// remove fragment from digest as it is used for re-signing purposes only
 	digestString := strings.TrimSuffix(string(digestData), ":"+t.GetUserFragment())
 	digest, err := hex.DecodeString(digestString)
@@ -12,7 +16,7 @@
 		return false, err
 	}

-	buff := bytes.NewBuffer(RemoveSignatureFromData(data))
+	buff := bytes.NewBuffer(content)
 	// if file has any imports process them
 	for _, file := range tmpl.GetFileImports() {
 		bin, err := os.ReadFile(file)

--- /dev/null
+++ b/pkg/templates/signer/tmpl_signer.go
@@ -0,0 +1,18 @@
+import (
+	"bytes"
+	"crypto/ecdsa"
+	"crypto/md5"
+	"crypto/rand"
+	"crypto/sha256"
+	"encoding/gob"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/projectdiscovery/gologger"
+	"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
+	errorutil "github.com/projectdiscovery/utils/errors"
+)

--- /dev/null
+++ b/pkg/templates/signer/tmpl_signer.go
@@ -0,0 +1,10 @@
+func ExtractSignatureAndContent(data []byte) (signature, content []byte) {
+	dataStr := string(data)
+	if idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {
+		signature = []byte(strings.TrimSpace(dataStr[idx:]))
+		content = []byte(strings.TrimSpace(dataStr[:idx]))
+	} else {
+		content = data
+	}
+	return
+}

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

cd /workspace/nuclei
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -timeout 30s -run ^TestTemplateSignerSignAndVerify$ github.com/projectdiscovery/nuclei/v3/pkg/templates/signer

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

cd /workspace/nuclei
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^()$' github.com/projectdiscovery/nuclei/v3/pkg/templates/signer


Node-RED-Dashboard before 2.26.2 allows ui_base/js/..%2f directory traversal to read files.

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.

module.exports = function(RED) {
    var ui = require('../ui')(RED);
    var path= require('path');
    var gsp = require.resolve('gridstack');
    var node;
    var set = RED.settings.ui || "{}";

    function BaseNode(config) {
        RED.nodes.createNode(this, config);
        node = this;
        var baseFontName = "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif";

        var defaultLightTheme = {
            baseColor: '#0094CE',
            baseFont: baseFontName
        }
        var defaultDarkTheme = {
            baseColor: '#097479',
            baseFont: baseFontName
        }
        var defaultCustomTheme = {
            name: 'Untitled Theme 1',
            baseColor: defaultLightTheme.baseColor,
            baseFont: baseFontName
        }
        var defaultAngularTheme = {
            primary:'indigo',
            accents:'teal',
            warn: "red",
            background:'grey'
        };

        // Setup theme name
        // First try old format (for upgrading with old flow file)
        // Then try new format
        // Else fallback to theme-light
        var themeName;
        if (typeof(config.theme) === 'string') { themeName = config.theme; }
        else { themeName = config.theme.name || "theme-light"; }

        // Setup other styles
        var defaultThemeState = {}
        if (themeName === 'theme-light') {
            defaultThemeState["base-font"] = {value: baseFontName};
            defaultThemeState["base-color"] = {value: "#0094CE"};
            defaultThemeState["page-backgroundColor"] = {value: "#fafafa"};
            defaultThemeState["page-titlebar-backgroundColor"] = {value: "#0094CE"};
            defaultThemeState["page-sidebar-backgroundColor"] = {value: "#ffffff"};
            defaultThemeState["group-backgroundColor"] = {value: "#ffffff"};
            defaultThemeState["group-textColor"] = {value: "#000000"};
            defaultThemeState["group-borderColor"] = {value: "#ffffff"};
            defaultThemeState["widget-textColor"] = {value: "#111111"};
            defaultThemeState["widget-backgroundColor"] = {value: "#0094CE"};
        }
        else {
            defaultThemeState["base-font"] = {value: baseFontName};
            defaultThemeState["base-color"] = {value: "#097479"};
            defaultThemeState["page-backgroundColor"] = {value: "#111111"};
            defaultThemeState["page-titlebar-backgroundColor"] = {value: "#097479"};
            defaultThemeState["page-sidebar-backgroundColor"] = {value: "#333333"};
            defaultThemeState["group-backgroundColor"] = {value: "#333333"};
            defaultThemeState["group-textColor"] = {value: "#10cfd8"};
            defaultThemeState["group-borderColor"] = {value: "#555555"};
            defaultThemeState["widget-textColor"] = {value: "#eeeeee"};
            defaultThemeState["widget-backgroundColor"] = {value: "#097479"};
        }

        var defaultThemeObject = {
            name: themeName,
            lightTheme: config.theme.lightTheme || defaultLightTheme,
            darkTheme: config.theme.darkTheme || defaultDarkTheme,
            customTheme: config.theme.customTheme || defaultCustomTheme,
            angularTheme: config.theme.angularTheme || defaultAngularTheme,
            themeState: config.theme.themeState || defaultThemeState
        }

        this.config = {
            theme: defaultThemeObject,
            site: config.site
        }
        ui.addBaseConfig(this.config);
    }
    RED.nodes.registerType("ui_base", BaseNode);

    RED.library.register("themes");

    RED.httpAdmin.get('/uisettings', function(req, res) {
        res.json(set);
    });

    var dname = path.normalize(path.join(__dirname , '../dist/'));
    var gspath = path.dirname(gsp);

    RED.httpAdmin.get('/ui_base/js/*', function(req, res) {
        var filename = path.join(__dirname , '../dist/js', req.params[0]);
        if (filename.indexOf(dname) !== 0) { res.sendStatus(404); }
        res.sendFile(filename, function (err) {
            if (err) {
                if (node) {
                    node.warn(filename + " not found. Maybe running in dev mode.");
                }
                else {
                    console.log("ui_base - error:",err);
                }
            }
        });
    });

    RED.httpAdmin.get('/ui_base/gs/*', function(req, res) {
        var filename = path.join(path.dirname(gsp), req.params[0]);
        if (filename.indexOf(gspath) !== 0) { res.sendStatus(404); }
        res.sendFile(filename, function (err) {
            if (err) {
                if (node) {
                    node.warn(filename + " not found. Maybe running in dev mode.");
                }
                else {
                    console.log("ui_base - error:",err);
                }
            }
        });
    });

    RED.httpAdmin.get('/ui_base/css/*', function(req, res) {
        var filename = path.join(__dirname , '../dist/css', req.params[0]);
        if (filename.indexOf(dname) !== 0) { res.sendStatus(404); }
        res.sendFile(filename, function (err) {
            if (err) {
                if (node) {
                    node.warn(filename + " not found. Maybe running in dev mode.");
                }
                else {
                    console.log("ui_base - error:",err);
                }
            }
        });
    });
};

module.exports = function(RED) {
    var ui = require('../ui')(RED);
    var path= require('path');
    var gsp = require.resolve('gridstack');
    var node;
    var uiset = RED.settings.ui || "{}";

    function BaseNode(config) {
        RED.nodes.createNode(this, config);
        node = this;
        var baseFontName = "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif";

        var defaultLightTheme = {
            baseColor: '#0094CE',
            baseFont: baseFontName
        }
        var defaultDarkTheme = {
            baseColor: '#097479',
            baseFont: baseFontName
        }
        var defaultCustomTheme = {
            name: 'Untitled Theme 1',
            baseColor: defaultLightTheme.baseColor,
            baseFont: baseFontName
        }
        var defaultAngularTheme = {
            primary:'indigo',
            accents:'teal',
            warn: "red",
            background:'grey'
        };

        // Setup theme name
        // First try old format (for upgrading with old flow file)
        // Then try new format
        // Else fallback to theme-light
        var themeName;
        if (typeof(config.theme) === 'string') { themeName = config.theme; }
        else { themeName = config.theme.name || "theme-light"; }

        // Setup other styles
        var defaultThemeState = {}
        if (themeName === 'theme-light') {
            defaultThemeState["base-font"] = {value: baseFontName};
            defaultThemeState["base-color"] = {value: "#0094CE"};
            defaultThemeState["page-backgroundColor"] = {value: "#fafafa"};
            defaultThemeState["page-titlebar-backgroundColor"] = {value: "#0094CE"};
            defaultThemeState["page-sidebar-backgroundColor"] = {value: "#ffffff"};
            defaultThemeState["group-backgroundColor"] = {value: "#ffffff"};
            defaultThemeState["group-textColor"] = {value: "#000000"};
            defaultThemeState["group-borderColor"] = {value: "#ffffff"};
            defaultThemeState["widget-textColor"] = {value: "#111111"};
            defaultThemeState["widget-backgroundColor"] = {value: "#0094CE"};
        }
        else {
            defaultThemeState["base-font"] = {value: baseFontName};
            defaultThemeState["base-color"] = {value: "#097479"};
            defaultThemeState["page-backgroundColor"] = {value: "#111111"};
            defaultThemeState["page-titlebar-backgroundColor"] = {value: "#097479"};
            defaultThemeState["page-sidebar-backgroundColor"] = {value: "#333333"};
            defaultThemeState["group-backgroundColor"] = {value: "#333333"};
            defaultThemeState["group-textColor"] = {value: "#10cfd8"};
            defaultThemeState["group-borderColor"] = {value: "#555555"};
            defaultThemeState["widget-textColor"] = {value: "#eeeeee"};
            defaultThemeState["widget-backgroundColor"] = {value: "#097479"};
        }

        var defaultThemeObject = {
            name: themeName,
            lightTheme: config.theme.lightTheme || defaultLightTheme,
            darkTheme: config.theme.darkTheme || defaultDarkTheme,
            customTheme: config.theme.customTheme || defaultCustomTheme,
            angularTheme: config.theme.angularTheme || defaultAngularTheme,
            themeState: config.theme.themeState || defaultThemeState
        }

        this.config = {
            theme: defaultThemeObject,
            site: config.site
        }
        ui.addBaseConfig(this.config);
    }
    RED.nodes.registerType("ui_base", BaseNode);

    RED.library.register("themes");

    RED.httpAdmin.get('/uisettings', function(req, res) {
        res.json(uiset);
    });

    const optsjs = { root: path.join(__dirname , '../dist/js'), dotfiles: 'deny' };
    const optscss = { root: path.join(__dirname , '../dist/css'), dotfiles: 'deny' };
    const optsgs = { root: path.dirname(gsp), dotfiles: 'deny' };

    RED.httpAdmin.get('/ui_base/js/*', function(req, res) {
        res.sendFile(req.params[0], optsjs, function (err) {
            if (err) {
                res.sendStatus(404);
                if (node) { node.warn("JS File not found."); }
                else { console.log("ui_base - error:",err); }
            }
        });
    });

    RED.httpAdmin.get('/ui_base/css/*', function(req, res) {
        res.sendFile(req.params[0], optscss, function (err) {
            if (err) {
                res.sendStatus(404);
                if (node) { node.warn("CSS File not found."); }
                else { console.log("ui_base - error:",err); }
            }
        });
    });

    RED.httpAdmin.get('/ui_base/gs/*', function(req, res) {
        res.sendFile(req.params[0], optsgs, function (err) {
            if (err) {
                res.sendStatus(404);
                if (node) { node.warn("Gridstack file not found."); }
                else { console.log("ui_base - error:",err); }
            }
        });
    });
};

--- a/nodes/ui_base.js
+++ b/nodes/ui_base.js
@@ -3,7 +3,7 @@
     var path= require('path');
     var gsp = require.resolve('gridstack');
     var node;
-    var set = RED.settings.ui || "{}";
+    var uiset = RED.settings.ui || "{}";

     function BaseNode(config) {
         RED.nodes.createNode(this, config);
@@ -85,53 +85,39 @@
     RED.library.register("themes");

     RED.httpAdmin.get('/uisettings', function(req, res) {
-        res.json(set);
+        res.json(uiset);
     });

-    var dname = path.normalize(path.join(__dirname , '../dist/'));
-    var gspath = path.dirname(gsp);
+    const optsjs = { root: path.join(__dirname , '../dist/js'), dotfiles: 'deny' };
+    const optscss = { root: path.join(__dirname , '../dist/css'), dotfiles: 'deny' };
+    const optsgs = { root: path.dirname(gsp), dotfiles: 'deny' };

     RED.httpAdmin.get('/ui_base/js/*', function(req, res) {
-        var filename = path.join(__dirname , '../dist/js', req.params[0]);
-        if (filename.indexOf(dname) !== 0) { res.sendStatus(404); }
-        res.sendFile(filename, function (err) {
+        res.sendFile(req.params[0], optsjs, function (err) {
             if (err) {
-                if (node) {
-                    node.warn(filename + " not found. Maybe running in dev mode.");
-                }
-                else {
-                    console.log("ui_base - error:",err);
-                }
+                res.sendStatus(404);
+                if (node) { node.warn("JS File not found."); }
+                else { console.log("ui_base - error:",err); }
+            }
+        });
+    });
+
+    RED.httpAdmin.get('/ui_base/css/*', function(req, res) {
+        res.sendFile(req.params[0], optscss, function (err) {
+            if (err) {
+                res.sendStatus(404);
+                if (node) { node.warn("CSS File not found."); }
+                else { console.log("ui_base - error:",err); }
             }
         });
     });

     RED.httpAdmin.get('/ui_base/gs/*', function(req, res) {
-        var filename = path.join(path.dirname(gsp), req.params[0]);
-        if (filename.indexOf(gspath) !== 0) { res.sendStatus(404); }
-        res.sendFile(filename, function (err) {
+        res.sendFile(req.params[0], optsgs, function (err) {
             if (err) {
-                if (node) {
-                    node.warn(filename + " not found. Maybe running in dev mode.");
-                }
-                else {
-                    console.log("ui_base - error:",err);
-                }
-            }
-        });
-    });
-
-    RED.httpAdmin.get('/ui_base/css/*', function(req, res) {
-        var filename = path.join(__dirname , '../dist/css', req.params[0]);
-        if (filename.indexOf(dname) !== 0) { res.sendStatus(404); }
-        res.sendFile(filename, function (err) {
-            if (err) {
-                if (node) {
-                    node.warn(filename + " not found. Maybe running in dev mode.");
-                }
-                else {
-                    console.log("ui_base - error:",err);
-                }
+                res.sendStatus(404);
+                if (node) { node.warn("Gridstack file not found."); }
+                else { console.log("ui_base - error:",err); }
             }
         });
     });


Jupyter Notebook before version 6.1.5 has an Open redirect vulnerability. A maliciously crafted link to a notebook server could redirect the browser to a different website. All notebook servers are technically affected, however, these maliciously crafted links can only be reasonably made for known notebook server hosts. A link to your notebook server may appear safe, but ultimately redirect to a spoofed server on the public internet. The issue is patched in version 6.1.5.

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 get(self):
        self.redirect(self.request.uri.rstrip('/'))

    def get(self):
        path, *rest = self.request.uri.partition("?")
        # trim trailing *and* leading /
        # to avoid misinterpreting repeated '//'
        path = "/" + path.strip("/")
        new_uri = "".join([path, *rest])
        self.redirect(new_uri)

--- a/notebook/base/handlers.py
+++ b/notebook/base/handlers.py
@@ -1,3 +1,7 @@
     def get(self):
-        self.redirect(self.request.uri.rstrip('/'))
-
+        path, *rest = self.request.uri.partition("?")
+        # trim trailing *and* leading /
+        # to avoid misinterpreting repeated '//'
+        path = "/" + path.strip("/")
+        new_uri = "".join([path, *rest])
+        self.redirect(new_uri)

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

cd /workspace/notebook
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2020-26215/bin/python -m pytest notebook/tests/test_paths.py::RedirectTestCase::test_trailing_slash -p no:warning --disable-warnings

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

cd /workspace/notebook
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2020-26215/bin/python -m pytest notebook/tests/test_paths.py -p no:warning --disable-warnings


Server-Side Request Forgery (SSRF) in GitHub repository owncast/owncast prior to 0.1.0.

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

func GetWebfingerLinks(account string) ([]map[string]interface{}, error) {
	type webfingerResponse struct {
		Links []map[string]interface{} `json:"links"`
	}

	account = strings.TrimLeft(account, "@") // remove any leading @
	accountComponents := strings.Split(account, "@")
	fediverseServer := accountComponents[1]

	// HTTPS is required.
	requestURL, err := url.Parse("https://" + fediverseServer)
	if err != nil {
		return nil, fmt.Errorf("unable to parse fediverse server host %s", fediverseServer)
	}

	requestURL.Path = "/.well-known/webfinger"
	query := requestURL.Query()
	query.Add("resource", fmt.Sprintf("acct:%s", account))
	requestURL.RawQuery = query.Encode()

	response, err := http.DefaultClient.Get(requestURL.String())
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	var links webfingerResponse
	decoder := json.NewDecoder(response.Body)
	if err := decoder.Decode(&links); err != nil {
		return nil, err
	}

	return links.Links, nil
}

func HandleCallbackCode(code, state string) (*Request, *Response, error) {
	request, exists := pendingAuthRequests[state]
	if !exists {
		return nil, nil, errors.New("no auth requests pending")
	}

	data := url.Values{}
	data.Set("grant_type", "authorization_code")
	data.Set("code", code)
	data.Set("client_id", request.ClientID)
	data.Set("redirect_uri", request.Callback.String())
	data.Set("code_verifier", request.CodeVerifier)

	client := &http.Client{}
	r, err := http.NewRequest("POST", request.Endpoint.String(), strings.NewReader(data.Encode())) // URL-encoded payload
	if err != nil {
		return nil, nil, err
	}
	r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

	res, err := client.Do(r)
	if err != nil {
		return nil, nil, err
	}
	defer res.Body.Close()
	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, nil, err
	}

	var response Response
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, nil, errors.Wrap(err, "unable to parse IndieAuth response: "+string(body))
	}

	if response.Error != "" || response.ErrorDescription != "" {
		errorText := makeIndieAuthClientErrorText(response.Error)
		log.Debugln("IndieAuth error:", response.Error, response.ErrorDescription)
		return nil, nil, fmt.Errorf("IndieAuth error: %s - %s", errorText, response.ErrorDescription)
	}

	// In case this IndieAuth server does not use OAuth error keys or has internal
	// issues resulting in unstructured errors.
	if res.StatusCode < 200 || res.StatusCode > 299 {
		log.Debugln("IndieAuth error. status code:", res.StatusCode, "body:", string(body))
		return nil, nil, errors.New("there was an error authenticating against IndieAuth server")
	}

	// Trim any trailing slash so we can accurately compare the two "me" values
	meResponseVerifier := strings.TrimRight(response.Me, "/")
	meRequestVerifier := strings.TrimRight(request.Me.String(), "/")

	// What we sent and what we got back must match
	if meRequestVerifier != meResponseVerifier {
		return nil, nil, errors.New("indieauth response does not match the initial anticipated auth destination")
	}

	return request, &response, nil
}

func GetWebfingerLinks(account string) ([]map[string]interface{}, error) {
	type webfingerResponse struct {
		Links []map[string]interface{} `json:"links"`
	}

	account = strings.TrimLeft(account, "@") // remove any leading @
	accountComponents := strings.Split(account, "@")
	fediverseServer := accountComponents[1]

	// HTTPS is required.
	requestURL, err := url.Parse("https://" + fediverseServer)
	if err != nil {
		return nil, fmt.Errorf("unable to parse fediverse server host %s", fediverseServer)
	}

	requestURL.Path = "/.well-known/webfinger"
	query := requestURL.Query()
	query.Add("resource", fmt.Sprintf("acct:%s", account))
	requestURL.RawQuery = query.Encode()

	// Do not support redirects.
	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	response, err := client.Get(requestURL.String())
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()

	var links webfingerResponse
	decoder := json.NewDecoder(response.Body)
	if err := decoder.Decode(&links); err != nil {
		return nil, err
	}

	return links.Links, nil
}

func HandleCallbackCode(code, state string) (*Request, *Response, error) {
	request, exists := pendingAuthRequests[state]
	if !exists {
		return nil, nil, errors.New("no auth requests pending")
	}

	data := url.Values{}
	data.Set("grant_type", "authorization_code")
	data.Set("code", code)
	data.Set("client_id", request.ClientID)
	data.Set("redirect_uri", request.Callback.String())
	data.Set("code_verifier", request.CodeVerifier)

	// Do not support redirects.
	client := &http.Client{
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}

	r, err := http.NewRequest("POST", request.Endpoint.String(), strings.NewReader(data.Encode())) // URL-encoded payload
	if err != nil {
		return nil, nil, err
	}
	r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

	res, err := client.Do(r)
	if err != nil {
		return nil, nil, err
	}
	defer res.Body.Close()
	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, nil, err
	}

	var response Response
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, nil, errors.Wrap(err, "unable to parse IndieAuth response: "+string(body))
	}

	if response.Error != "" || response.ErrorDescription != "" {
		errorText := makeIndieAuthClientErrorText(response.Error)
		log.Debugln("IndieAuth error:", response.Error, response.ErrorDescription)
		return nil, nil, fmt.Errorf("IndieAuth error: %s - %s", errorText, response.ErrorDescription)
	}

	// In case this IndieAuth server does not use OAuth error keys or has internal
	// issues resulting in unstructured errors.
	if res.StatusCode < 200 || res.StatusCode > 299 {
		log.Debugln("IndieAuth error. status code:", res.StatusCode, "body:", string(body))
		return nil, nil, errors.New("there was an error authenticating against IndieAuth server")
	}

	// Trim any trailing slash so we can accurately compare the two "me" values
	meResponseVerifier := strings.TrimRight(response.Me, "/")
	meRequestVerifier := strings.TrimRight(request.Me.String(), "/")

	// What we sent and what we got back must match
	if meRequestVerifier != meResponseVerifier {
		return nil, nil, errors.New("indieauth response does not match the initial anticipated auth destination")
	}

	return request, &response, nil
}

--- a/activitypub/webfinger/webfinger.go
+++ b/activitypub/webfinger/webfinger.go
@@ -18,7 +18,14 @@
 	query.Add("resource", fmt.Sprintf("acct:%s", account))
 	requestURL.RawQuery = query.Encode()

-	response, err := http.DefaultClient.Get(requestURL.String())
+	// Do not support redirects.
+	client := &http.Client{
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse
+		},
+	}
+
+	response, err := client.Get(requestURL.String())
 	if err != nil {
 		return nil, err
 	}

--- a/auth/indieauth/client.go
+++ b/auth/indieauth/client.go
@@ -11,7 +11,13 @@
 	data.Set("redirect_uri", request.Callback.String())
 	data.Set("code_verifier", request.CodeVerifier)

-	client := &http.Client{}
+	// Do not support redirects.
+	client := &http.Client{
+		CheckRedirect: func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse
+		},
+	}
+
 	r, err := http.NewRequest("POST", request.Endpoint.String(), strings.NewReader(data.Encode())) // URL-encoded payload
 	if err != nil {
 		return nil, nil, err


ScanCode.io is a server to script and automate software composition analysis with ScanPipe pipelines. Prior to version 32.5.1, the software has a possible command injection vulnerability in the docker fetch process as it allows to append malicious commands in the `docker_reference` parameter.

In the function `scanpipe/pipes/fetch.py:fetch_docker_image` the parameter `docker_reference` is user controllable. The `docker_reference` variable is then passed to the vulnerable function `get_docker_image_platform`.  However, the `get_docker_image_plaform` function constructs a shell command with the passed `docker_reference`. The `pipes.run_command` then executes the shell command without any prior sanitization, making the function vulnerable to command injections. A malicious user who is able to create or add inputs to a project can inject commands. Although the command injections are blind and the user will not receive direct feedback without logs, it is still possible to cause damage to the server/container. The vulnerability appears for example if a malicious user adds a semicolon after the input of `docker://;`, it would allow appending malicious commands.

Version 32.5.1 contains a patch for this issue. The `docker_reference` input should be sanitized to avoid command injections and, as a workaround, one may avoid creating commands with user controlled input directly.

def run_command(cmd, log_output=False):
    """
    Return (exitcode, output) of executing the provided `cmd` in a shell.
    `cmd` can be provided as a string or as a list of arguments.

    If `log_output` is True, the stdout and stderr of the process will be captured
    and streamed to the `logger`.
    """
    if isinstance(cmd, list):
        cmd = " ".join(cmd)

    if not log_output:
        exitcode, output = subprocess.getstatusoutput(cmd)
        return exitcode, output

    process = subprocess.Popen(
        cmd,
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        universal_newlines=True,
    )

    while _stream_process(process):
        sleep(1)

    exitcode = process.poll()
    return exitcode, ""

def fetch_docker_image(docker_reference, to=None):
    """
    Fetch a docker image from the provided Docker image `docker_reference`
    docker:// reference URL. Return a `download` object.

    Docker references are documented here:
    https://github.com/containers/skopeo/blob/0faf16017/docs/skopeo.1.md#image-names
    """
    name = python_safe_name(docker_reference.replace("docker://", ""))
    filename = f"{name}.tar"
    download_directory = to or tempfile.mkdtemp()
    output_file = Path(download_directory, filename)
    target = f"docker-archive:{output_file}"

    skopeo_executable = _get_skopeo_location()
    platform_args = []
    platform = get_docker_image_platform(docker_reference)
    if platform:
        os, arch, variant = platform
        if os:
            platform_args.append(f"--override-os={os}")
        if arch:
            platform_args.append(f"--override-arch={arch}")
        if variant:
            platform_args.append(f"--override-variant={variant}")
    platform_args = " ".join(platform_args)

    cmd = (
        f"{skopeo_executable} copy --insecure-policy "
        f"{platform_args} {docker_reference} {target}"
    )
    logger.info(f"Fetching image with: {cmd}")
    exitcode, output = pipes.run_command(cmd)
    logger.info(output)
    if exitcode != 0:
        raise FetchDockerImageError(output)

    checksums = multi_checksums(output_file, ("md5", "sha1"))

    return Download(
        uri=docker_reference,
        directory=download_directory,
        filename=filename,
        path=output_file,
        size=output_file.stat().st_size,
        sha1=checksums["sha1"],
        md5=checksums["md5"],
    )

def run_command(cmd, log_output=False):
    """
    Return (exitcode, output) of executing the provided `cmd` in a shell.
    `cmd` can be provided as a string or as a list of arguments.

    If `log_output` is True, the stdout and stderr of the process will be captured
    and streamed to the `logger`.
    """
    if isinstance(cmd, list):
        cmd = " ".join(cmd)

    if not log_output:
        exitcode, output = subprocess.getstatusoutput(cmd)
        return exitcode, output

    process = subprocess.Popen(
        cmd,
        shell=False,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        universal_newlines=True,
    )

    while _stream_process(process):
        sleep(1)

    exitcode = process.poll()
    return exitcode, ""

def fetch_docker_image(docker_reference, to=None):
    """
    Fetch a docker image from the provided Docker image `docker_reference`
    docker:// reference URL. Return a `download` object.

    Docker references are documented here:
    https://github.com/containers/skopeo/blob/0faf16017/docs/skopeo.1.md#image-names
    """

    whitelist = r"^docker://[a-zA-Z0-9_.:/@-]+$"
    if not re.match(whitelist, docker_reference):
        raise ValueError("Invalid Docker reference.")

    name = python_safe_name(docker_reference.replace("docker://", ""))
    filename = f"{name}.tar"
    download_directory = to or tempfile.mkdtemp()
    output_file = Path(download_directory, filename)
    target = f"docker-archive:{output_file}"

    skopeo_executable = _get_skopeo_location()
    platform_args = []
    platform = get_docker_image_platform(docker_reference)
    if platform:
        os, arch, variant = platform
        if os:
            platform_args.append(f"--override-os={os}")
        if arch:
            platform_args.append(f"--override-arch={arch}")
        if variant:
            platform_args.append(f"--override-variant={variant}")
    platform_args = " ".join(platform_args)

    cmd = (
        f"{skopeo_executable} copy --insecure-policy "
        f"{platform_args} {docker_reference} {target}"
    )
    logger.info(f"Fetching image with: {cmd}")
    exitcode, output = pipes.run_command(cmd)
    logger.info(output)
    if exitcode != 0:
        raise FetchDockerImageError(output)

    checksums = multi_checksums(output_file, ("md5", "sha1"))

    return Download(
        uri=docker_reference,
        directory=download_directory,
        filename=filename,
        path=output_file,
        size=output_file.stat().st_size,
        sha1=checksums["sha1"],
        md5=checksums["md5"],
    )

--- a/scanpipe/pipes/__init__.py
+++ b/scanpipe/pipes/__init__.py
@@ -15,7 +15,7 @@

     process = subprocess.Popen(
         cmd,
-        shell=True,
+        shell=False,
         stdout=subprocess.PIPE,
         stderr=subprocess.STDOUT,
         universal_newlines=True,

--- a/scanpipe/pipes/fetch.py
+++ b/scanpipe/pipes/fetch.py
@@ -6,6 +6,11 @@
     Docker references are documented here:
     https://github.com/containers/skopeo/blob/0faf16017/docs/skopeo.1.md#image-names
     """
+
+    whitelist = r"^docker://[a-zA-Z0-9_.:/@-]+$"
+    if not re.match(whitelist, docker_reference):
+        raise ValueError("Invalid Docker reference.")
+
     name = python_safe_name(docker_reference.replace("docker://", ""))
     filename = f"{name}.tar"
     download_directory = to or tempfile.mkdtemp()


Vyper is a Pythonic smart contract language for the Ethereum virtual machine. Prior to version 0.3.8, due to missing overflow check for loop variables, by assigning the iterator of a loop to a variable, it is possible to overflow the type of the latter. The issue seems to happen only in loops of type `for i in range(a, a + N)` as in loops of type `for i in range(start, stop)` and `for i in range(stop)`, the compiler is able to raise a `TypeMismatch` when trying to overflow the variable. The problem has been patched in version 0.3.8.

Integer Overflow or Wraparound

The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.

    def _parse_For_range(self):
        # TODO make sure type always gets annotated
        if "type" in self.stmt.target._metadata:
            iter_typ = self.stmt.target._metadata["type"]
        else:
            iter_typ = INT256_T

        # Get arg0
        arg0 = self.stmt.iter.args[0]
        num_of_args = len(self.stmt.iter.args)

        # Type 1 for, e.g. for i in range(10): ...
        if num_of_args == 1:
            arg0_val = self._get_range_const_value(arg0)
            start = IRnode.from_list(0, typ=iter_typ)
            rounds = arg0_val

        # Type 2 for, e.g. for i in range(100, 110): ...
        elif self._check_valid_range_constant(self.stmt.iter.args[1]).is_literal:
            arg0_val = self._get_range_const_value(arg0)
            arg1_val = self._get_range_const_value(self.stmt.iter.args[1])
            start = IRnode.from_list(arg0_val, typ=iter_typ)
            rounds = IRnode.from_list(arg1_val - arg0_val, typ=iter_typ)

        # Type 3 for, e.g. for i in range(x, x + 10): ...
        else:
            arg1 = self.stmt.iter.args[1]
            rounds = self._get_range_const_value(arg1.right)
            start = Expr.parse_value_expr(arg0, self.context)

        r = rounds if isinstance(rounds, int) else rounds.value
        if r < 1:
            return

        varname = self.stmt.target.id
        i = IRnode.from_list(self.context.fresh_varname("range_ix"), typ=UINT256_T)
        iptr = self.context.new_variable(varname, iter_typ)

        self.context.forvars[varname] = True

        loop_body = ["seq"]
        # store the current value of i so it is accessible to userland
        loop_body.append(["mstore", iptr, i])
        loop_body.append(parse_body(self.stmt.body, self.context))

        ir_node = IRnode.from_list(["repeat", i, start, rounds, rounds, loop_body])
        del self.context.forvars[varname]

        return ir_node

    def _parse_For_range(self):
        # TODO make sure type always gets annotated
        if "type" in self.stmt.target._metadata:
            iter_typ = self.stmt.target._metadata["type"]
        else:
            iter_typ = INT256_T

        # Get arg0
        arg0 = self.stmt.iter.args[0]
        num_of_args = len(self.stmt.iter.args)

        # Type 1 for, e.g. for i in range(10): ...
        if num_of_args == 1:
            arg0_val = self._get_range_const_value(arg0)
            start = IRnode.from_list(0, typ=iter_typ)
            rounds = arg0_val

        # Type 2 for, e.g. for i in range(100, 110): ...
        elif self._check_valid_range_constant(self.stmt.iter.args[1]).is_literal:
            arg0_val = self._get_range_const_value(arg0)
            arg1_val = self._get_range_const_value(self.stmt.iter.args[1])
            start = IRnode.from_list(arg0_val, typ=iter_typ)
            rounds = IRnode.from_list(arg1_val - arg0_val, typ=iter_typ)

        # Type 3 for, e.g. for i in range(x, x + 10): ...
        else:
            arg1 = self.stmt.iter.args[1]
            rounds = self._get_range_const_value(arg1.right)
            start = Expr.parse_value_expr(arg0, self.context)
            _, hi = start.typ.int_bounds
            start = clamp("le", start, hi + 1 - rounds)

        r = rounds if isinstance(rounds, int) else rounds.value
        if r < 1:
            return

        varname = self.stmt.target.id
        i = IRnode.from_list(self.context.fresh_varname("range_ix"), typ=UINT256_T)
        iptr = self.context.new_variable(varname, iter_typ)

        self.context.forvars[varname] = True

        loop_body = ["seq"]
        # store the current value of i so it is accessible to userland
        loop_body.append(["mstore", iptr, i])
        loop_body.append(parse_body(self.stmt.body, self.context))

        ir_node = IRnode.from_list(["repeat", i, start, rounds, rounds, loop_body])
        del self.context.forvars[varname]

        return ir_node

--- a/vyper/codegen/stmt.py
+++ b/vyper/codegen/stmt.py
@@ -27,6 +27,8 @@
             arg1 = self.stmt.iter.args[1]
             rounds = self._get_range_const_value(arg1.right)
             start = Expr.parse_value_expr(arg0, self.context)
+            _, hi = start.typ.int_bounds
+            start = clamp("le", start, hi + 1 - rounds)

         r = rounds if isinstance(rounds, int) else rounds.value
         if r < 1:


In version 2.9.0.beta14 of Discourse, an open-source discussion platform, maliciously embedded urls can leak an admin's digest of recent topics, possibly exposing private information. A patch is available for version 2.9.0.beta15. There are no known workarounds for this issue.

Exposure of Sensitive Information to an Unauthorized Actor

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.

EmailPreview.reopenClass({
  findDigest(username, lastSeenAt) {
    return ajax("/admin/email/preview-digest.json", {
      data: { last_seen_at: lastSeenAt || oneWeekAgo(), username },
    }).then((result) => EmailPreview.create(result));
  },

  sendDigest(username, lastSeenAt, email) {
    return ajax("/admin/email/send-digest.json", {
      data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
    });
  },
});

EmailPreview.reopenClass({
  findDigest(username, lastSeenAt) {
    return ajax("/admin/email/preview-digest.json", {
      data: { last_seen_at: lastSeenAt || oneWeekAgo(), username },
    }).then((result) => EmailPreview.create(result));
  },

  sendDigest(username, lastSeenAt, email) {
    return ajax("/admin/email/send-digest.json", {
      type: "POST",
      data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
    });
  },
});

--- a/app/assets/javascripts/admin/addon/models/email-preview.js
+++ b/app/assets/javascripts/admin/addon/models/email-preview.js
@@ -7,6 +7,7 @@

   sendDigest(username, lastSeenAt, email) {
     return ajax("/admin/email/send-digest.json", {
+      type: "POST",
       data: { last_seen_at: lastSeenAt || oneWeekAgo(), username, email },
     });
   },


A directory traversal vulnerability was found in the ClairCore engine of Clair. An attacker can exploit this by supplying a crafted container image which, when scanned by Clair, allows for arbitrary file write on the filesystem, potentially allowing for remote code execution.

func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	defer trace.StartRegion(ctx, "Scanner.Scan").End()
	trace.Log(ctx, "layer", layer.Hash.String())
	ctx = baggage.ContextWithValues(ctx,
		label.String("component", "rpm/Scanner.Scan"),
		label.String("version", ps.Version()),
		label.String("layer", layer.Hash.String()))
	zlog.Debug(ctx).Msg("start")
	defer zlog.Debug(ctx).Msg("done")

	r, err := layer.Reader()
	if err != nil {
		return nil, err
	}
	rd, ok := r.(interface {
		io.ReadCloser
		io.Seeker
	})
	if !ok {
		return nil, errors.New("rpm: cannot seek on returned layer Reader")
	}

	// Map of directory to confidence score. Confidence of len(dbnames) means
	// it's almost certainly an rpm database.
	possible := make(map[string]int)
	tr := tar.NewReader(rd)
	// Find possible rpm dbs
	// If none found, return
	var h *tar.Header
	for h, err = tr.Next(); err == nil; h, err = tr.Next() {
		n := filepath.Base(h.Name)
		d := filepath.Dir(h.Name)
		if _, ok := dbnames[n]; ok {
			possible[d]++
		}
	}
	if err != io.EOF {
		return nil, err
	}
	if len(possible) == 0 {
		return nil, nil
	}
	found := make([]string, 0)
	for k, score := range possible {
		if score == len(dbnames) {
			found = append(found, filepath.Join("/", k))
		}
	}
	zlog.Debug(ctx).Int("count", len(found)).Msg("found possible databases")
	if len(found) == 0 {
		return nil, nil
	}

	root, err := ioutil.TempDir("", "rpmscanner.")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := os.RemoveAll(root); err != nil {
			// Raising an error should notify an operator?
			//
			// It's this or panic.
			zlog.Error(ctx).Err(err).Msg("error removing extracted files")
		}
	}()
	empty := filepath.Join(os.TempDir(), "rpm.emptyfile")
	ef, err := os.Create(empty)
	if err != nil {
		return nil, err
	}
	if err := ef.Close(); err != nil {
		return nil, err
	}

	// Extract tarball
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	if _, err := rd.Seek(0, io.SeekStart); err != nil {
		return nil, fmt.Errorf("rpm: unable to seek: %w", err)
	}
	tr = tar.NewReader(rd)
	const (
		// Any mode bits need to be or'd with these constants so that this
		// process can always remove and traverse files it writes.
		dirMode  = 0o0700
		fileMode = 0o0600
	)
	// For logging what we've done.
	var stats struct {
		Reg, Link, Symlink, Dir, Whiteout int
		OutOfOrder                        int
	}
	// Made tracks directory creation to prevent excessive mkdir calls.
	made := map[string]struct{}{root: {}}
	// DeferLn is for queuing up out-of-order hard links.
	var deferLn [][2]string
	for h, err = tr.Next(); err == nil; h, err = tr.Next() {
		if strings.HasPrefix(filepath.Base(h.Name), ".wh.") {
			// Whiteout, skip.
			stats.Whiteout++
			continue
		}
		// Build the path on the filesystem.
		tgt := filepath.Join(root, filepath.Clean(h.Name))
		// Since tar, as a format, doesn't impose ordering requirements, make
		// sure to create all parent directories of the current entry.
		d := filepath.Dir(tgt)
		if _, ok := made[d]; !ok {
			if err := os.MkdirAll(d, dirMode); err != nil {
				return nil, err
			}
			made[d] = struct{}{}
			stats.OutOfOrder++
		}

		// Populate the target file.
		var err error
		switch h.Typeflag {
		case tar.TypeDir:
			m := h.FileInfo().Mode() | dirMode
			if _, ok := made[tgt]; ok {
				// If we had made this directory by seeing a child first, touch
				// up the permissions.
				err = os.Chmod(tgt, m)
				break
			}
			err = os.Mkdir(tgt, m)
			// Make sure to preempt the MkdirAll call if the entries were
			// ordered nicely.
			made[d] = struct{}{}
			stats.Dir++
		case tar.TypeReg:
			m := h.FileInfo().Mode() | fileMode
			var f *os.File
			f, err = os.OpenFile(tgt, os.O_CREATE|os.O_WRONLY, m)
			if err != nil {
				break // Handle after the switch.
			}
			_, err = io.Copy(f, tr)
			if err := f.Close(); err != nil {
				zlog.Warn(ctx).Err(err).Msg("error closing new file")
			}
			stats.Reg++
		case tar.TypeSymlink:
			// Normalize the link target into the root.
			ln := filepath.Join(root, filepath.Clean(h.Linkname))
			err = os.Symlink(ln, tgt)
			stats.Symlink++
		case tar.TypeLink:
			// Normalize the link target into the root.
			ln := filepath.Join(root, filepath.Clean(h.Linkname))
			_, exists := os.Lstat(ln)
			switch {
			case errors.Is(exists, nil):
				err = os.Link(ln, tgt)
			case errors.Is(exists, os.ErrNotExist):
				// Push onto a queue to fix later. Link(2) is documented to need
				// a valid target, unlike symlink(2), which allows a missing
				// target. Combined with tar's lack of ordering, this seems like
				// the best solution.
				deferLn = append(deferLn, [2]string{ln, tgt})
			default:
				err = exists
			}
			stats.Link++
		default:
			// Skip everything else: Can't mknod as an unprivileged user and
			// fifos are only useful to a running system.
		}
		if err != nil {
			return nil, err
		}
	}
	for _, l := range deferLn {
		if err := os.Link(l[0], l[1]); err != nil {
			zlog.Debug(ctx).
				Err(err).
				Msg("cross-layer (or invalid) hardlink found")
			if err := os.Link(empty, l[1]); err != nil {
				return nil, err
			}
		}
	}
	if ct := len(deferLn); ct != 0 {
		zlog.Debug(ctx).
			Int("count", ct).
			Msg("processed deferred links")
	}

	zlog.Info(ctx).
		Int("file", stats.Reg).
		Int("dir", stats.Dir).
		Int("dir(out of order)", stats.OutOfOrder).
		Int("symlink", stats.Symlink).
		Int("link", stats.Link).
		Int("whiteout", stats.Whiteout).
		Msg("extracted layer")

	var pkgs []*claircore.Package
	// Using --root and --dbpath, run rpm query on every suspected database
	for _, db := range found {
		zlog.Debug(ctx).Str("db", db).Msg("examining database")

		cmd := exec.CommandContext(ctx, "rpm",
			`--root`, root, `--dbpath`, db,
			`--query`, `--all`, `--queryformat`, queryFmt)
		r, err := cmd.StdoutPipe()
		if err != nil {
			return nil, err
		}
		errbuf := bytes.Buffer{}
		cmd.Stderr = &errbuf
		zlog.Debug(ctx).Str("db", db).Strs("cmd", cmd.Args).Msg("rpm invocation")
		if err := cmd.Start(); err != nil {
			r.Close()
			return nil, err
		}
		// Use a closure to defer the Close call.
		if err := func() error {
			defer r.Close()
			srcs := make(map[string]*claircore.Package)
			s := bufio.NewScanner(r)
			s.Split(querySplit)

			for s.Scan() {
				p, err := parsePackage(ctx, srcs, bytes.NewBuffer(s.Bytes()))
				if err != nil {
					return err
				}
				p.PackageDB = db
				pkgs = append(pkgs, p)
			}

			return s.Err()
		}(); err != nil {
			if errbuf.Len() != 0 {
				zlog.Warn(ctx).
					Str("db", db).
					Strs("cmd", cmd.Args).
					Str("err", errbuf.String()).
					Msg("error output")
			}
			return nil, fmt.Errorf("rpm: error reading rpm output: %w", err)
		}
		if err := cmd.Wait(); err != nil {
			return nil, err
		}
	}

	return pkgs, nil
}

func (ps *Scanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	defer trace.StartRegion(ctx, "Scanner.Scan").End()
	trace.Log(ctx, "layer", layer.Hash.String())
	ctx = baggage.ContextWithValues(ctx,
		label.String("component", "rpm/Scanner.Scan"),
		label.String("version", ps.Version()),
		label.String("layer", layer.Hash.String()))
	zlog.Debug(ctx).Msg("start")
	defer zlog.Debug(ctx).Msg("done")

	r, err := layer.Reader()
	if err != nil {
		return nil, err
	}
	rd, ok := r.(interface {
		io.ReadCloser
		io.Seeker
	})
	if !ok {
		return nil, errors.New("rpm: cannot seek on returned layer Reader")
	}

	// Map of directory to confidence score. Confidence of len(dbnames) means
	// it's almost certainly an rpm database.
	possible := make(map[string]int)
	tr := tar.NewReader(rd)
	// Find possible rpm dbs
	// If none found, return
	var h *tar.Header
	for h, err = tr.Next(); err == nil; h, err = tr.Next() {
		n := filepath.Base(h.Name)
		d := filepath.Dir(h.Name)
		if _, ok := dbnames[n]; ok {
			possible[d]++
		}
	}
	if err != io.EOF {
		return nil, err
	}
	if len(possible) == 0 {
		return nil, nil
	}
	found := make([]string, 0)
	for k, score := range possible {
		if score == len(dbnames) {
			found = append(found, filepath.Join("/", k))
		}
	}
	zlog.Debug(ctx).Int("count", len(found)).Msg("found possible databases")
	if len(found) == 0 {
		return nil, nil
	}

	root, err := ioutil.TempDir("", "rpmscanner.")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := os.RemoveAll(root); err != nil {
			// Raising an error should notify an operator?
			//
			// It's this or panic.
			zlog.Error(ctx).Err(err).Msg("error removing extracted files")
		}
	}()
	empty := filepath.Join(os.TempDir(), "rpm.emptyfile")
	ef, err := os.Create(empty)
	if err != nil {
		return nil, err
	}
	if err := ef.Close(); err != nil {
		return nil, err
	}

	// Extract tarball
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	if _, err := rd.Seek(0, io.SeekStart); err != nil {
		return nil, fmt.Errorf("rpm: unable to seek: %w", err)
	}
	tr = tar.NewReader(rd)
	const (
		// Any mode bits need to be or'd with these constants so that this
		// process can always remove and traverse files it writes.
		dirMode  = 0o0700
		fileMode = 0o0600
	)
	// For logging what we've done.
	var stats struct {
		Reg, Link, Symlink, Dir, Whiteout int
		OutOfOrder                        int
	}
	// Made tracks directory creation to prevent excessive mkdir calls.
	made := map[string]struct{}{root: {}}
	// DeferLn is for queuing up out-of-order hard links.
	var deferLn [][2]string
	for h, err = tr.Next(); err == nil; h, err = tr.Next() {
		if strings.HasPrefix(filepath.Base(h.Name), ".wh.") {
			// Whiteout, skip.
			stats.Whiteout++
			continue
		}
		// Build the path on the filesystem.
		tgt := relPath(root, h.Name)
		// Since tar, as a format, doesn't impose ordering requirements, make
		// sure to create all parent directories of the current entry.
		d := filepath.Dir(tgt)
		if _, ok := made[d]; !ok {
			if err := os.MkdirAll(d, dirMode); err != nil {
				return nil, err
			}
			made[d] = struct{}{}
			stats.OutOfOrder++
		}

		// Populate the target file.
		var err error
		switch h.Typeflag {
		case tar.TypeDir:
			m := h.FileInfo().Mode() | dirMode
			if _, ok := made[tgt]; ok {
				// If we had made this directory by seeing a child first, touch
				// up the permissions.
				err = os.Chmod(tgt, m)
				break
			}
			err = os.Mkdir(tgt, m)
			// Make sure to preempt the MkdirAll call if the entries were
			// ordered nicely.
			made[d] = struct{}{}
			stats.Dir++
		case tar.TypeReg:
			m := h.FileInfo().Mode() | fileMode
			var f *os.File
			f, err = os.OpenFile(tgt, os.O_CREATE|os.O_WRONLY, m)
			if err != nil {
				break // Handle after the switch.
			}
			_, err = io.Copy(f, tr)
			if err := f.Close(); err != nil {
				zlog.Warn(ctx).Err(err).Msg("error closing new file")
			}
			stats.Reg++
		case tar.TypeSymlink:
			// Normalize the link target into the root.
			ln := relPath(root, h.Linkname)
			err = os.Symlink(ln, tgt)
			stats.Symlink++
		case tar.TypeLink:
			// Normalize the link target into the root.
			ln := relPath(root, h.Linkname)
			_, exists := os.Lstat(ln)
			switch {
			case errors.Is(exists, nil):
				err = os.Link(ln, tgt)
			case errors.Is(exists, os.ErrNotExist):
				// Push onto a queue to fix later. Link(2) is documented to need
				// a valid target, unlike symlink(2), which allows a missing
				// target. Combined with tar's lack of ordering, this seems like
				// the best solution.
				deferLn = append(deferLn, [2]string{ln, tgt})
			default:
				err = exists
			}
			stats.Link++
		default:
			// Skip everything else: Can't mknod as an unprivileged user and
			// fifos are only useful to a running system.
		}
		if err != nil {
			return nil, err
		}
	}
	for _, l := range deferLn {
		if err := os.Link(l[0], l[1]); err != nil {
			zlog.Debug(ctx).
				Err(err).
				Msg("cross-layer (or invalid) hardlink found")
			if err := os.Link(empty, l[1]); err != nil {
				return nil, err
			}
		}
	}
	if ct := len(deferLn); ct != 0 {
		zlog.Debug(ctx).
			Int("count", ct).
			Msg("processed deferred links")
	}

	zlog.Info(ctx).
		Int("file", stats.Reg).
		Int("dir", stats.Dir).
		Int("dir(out of order)", stats.OutOfOrder).
		Int("symlink", stats.Symlink).
		Int("link", stats.Link).
		Int("whiteout", stats.Whiteout).
		Msg("extracted layer")

	var pkgs []*claircore.Package
	// Using --root and --dbpath, run rpm query on every suspected database
	for _, db := range found {
		zlog.Debug(ctx).Str("db", db).Msg("examining database")

		cmd := exec.CommandContext(ctx, "rpm",
			`--root`, root, `--dbpath`, db,
			`--query`, `--all`, `--queryformat`, queryFmt)
		r, err := cmd.StdoutPipe()
		if err != nil {
			return nil, err
		}
		errbuf := bytes.Buffer{}
		cmd.Stderr = &errbuf
		zlog.Debug(ctx).Str("db", db).Strs("cmd", cmd.Args).Msg("rpm invocation")
		if err := cmd.Start(); err != nil {
			r.Close()
			return nil, err
		}
		// Use a closure to defer the Close call.
		if err := func() error {
			defer r.Close()
			srcs := make(map[string]*claircore.Package)
			s := bufio.NewScanner(r)
			s.Split(querySplit)

			for s.Scan() {
				p, err := parsePackage(ctx, srcs, bytes.NewBuffer(s.Bytes()))
				if err != nil {
					return err
				}
				p.PackageDB = db
				pkgs = append(pkgs, p)
			}

			return s.Err()
		}(); err != nil {
			if errbuf.Len() != 0 {
				zlog.Warn(ctx).
					Str("db", db).
					Strs("cmd", cmd.Args).
					Str("err", errbuf.String()).
					Msg("error output")
			}
			return nil, fmt.Errorf("rpm: error reading rpm output: %w", err)
		}
		if err := cmd.Wait(); err != nil {
			return nil, err
		}
	}

	return pkgs, nil
}

--- a/rpm/packagescanner.go
+++ b/rpm/packagescanner.go
@@ -105,7 +105,7 @@
 			continue
 		}
 		// Build the path on the filesystem.
-		tgt := filepath.Join(root, filepath.Clean(h.Name))
+		tgt := relPath(root, h.Name)
 		// Since tar, as a format, doesn't impose ordering requirements, make
 		// sure to create all parent directories of the current entry.
 		d := filepath.Dir(tgt)
@@ -147,12 +147,12 @@
 			stats.Reg++
 		case tar.TypeSymlink:
 			// Normalize the link target into the root.
-			ln := filepath.Join(root, filepath.Clean(h.Linkname))
+			ln := relPath(root, h.Linkname)
 			err = os.Symlink(ln, tgt)
 			stats.Symlink++
 		case tar.TypeLink:
 			// Normalize the link target into the root.
-			ln := filepath.Join(root, filepath.Clean(h.Linkname))
+			ln := relPath(root, h.Linkname)
 			_, exists := os.Lstat(ln)
 			switch {
 			case errors.Is(exists, nil):


flask-session-captcha is a package which allows users to extend Flask by adding an image based captcha stored in a server side session. In versions prior to 1.2.1, he `captcha.validate()` function would return `None` if passed no value (e.g. by submitting an having an empty form). If implementing users were checking the return value to be **False**, the captcha verification check could be bypassed. Version 1.2.1 fixes the issue. Users can workaround the issue by not explicitly checking that the value is False. Checking the return value less explicitly should still work.

Improper Check for Unusual or Exceptional Conditions

The product does not check or incorrectly checks for unusual or exceptional conditions that are not expected to occur frequently during day to day operation of the product.

    def test_captcha_validate_value(self):
        captcha = FlaskSessionCaptcha(self.app)
        _default_routes(captcha, self.app)

        with self.app.test_request_context('/'):
            captcha.generate()
            answer = captcha.get_answer()
            assert not captcha.validate(value="wrong")
            captcha.generate()
            answer = captcha.get_answer()
            assert captcha.validate(value=answer)

    def test_captcha_validate_value(self):
        captcha = FlaskSessionCaptcha(self.app)
        _default_routes(captcha, self.app)

        with self.app.test_request_context('/'):
            captcha.generate()
            answer = captcha.get_answer()
            assert captcha.validate(value=None) == False
            captcha.generate()
            answer = captcha.get_answer()
            assert captcha.validate(value="") == False
            captcha.generate()
            answer = captcha.get_answer()
            assert captcha.validate(value="wrong") == False
            captcha.generate()
            answer = captcha.get_answer()
            assert captcha.validate(value=answer)

--- a/test_flask_session_captcha.py
+++ b/test_flask_session_captcha.py
@@ -5,7 +5,13 @@
         with self.app.test_request_context('/'):
             captcha.generate()
             answer = captcha.get_answer()
-            assert not captcha.validate(value="wrong")
+            assert captcha.validate(value=None) == False
+            captcha.generate()
+            answer = captcha.get_answer()
+            assert captcha.validate(value="") == False
+            captcha.generate()
+            answer = captcha.get_answer()
+            assert captcha.validate(value="wrong") == False
             captcha.generate()
             answer = captcha.get_answer()
             assert captcha.validate(value=answer)


Due to improper sanitization of user input on Windows, the static file handler allows for directory traversal, allowing an attacker to read files outside of the target directory that the server has permission to read.

func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
	h := func(c Context) error {
		p, err := url.PathUnescape(c.Param("*"))
		if err != nil {
			return err
		}

		name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
		fi, err := os.Stat(name)
		if err != nil {
			// The access path does not exist
			return NotFoundHandler(c)
		}

		// If the request is for a directory and does not end with "/"
		p = c.Request().URL.Path // path must not be empty.
		if fi.IsDir() && p[len(p)-1] != '/' {
			// Redirect to ends with "/"
			return c.Redirect(http.StatusMovedPermanently, p+"/")
		}
		return c.File(name)
	}
	if prefix == "/" {
		return get(prefix+"*", h)
	}
	return get(prefix+"/*", h)
}

func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
	// Defaults
	if config.Root == "" {
		config.Root = "." // For security we want to restrict to CWD.
	}
	if config.Skipper == nil {
		config.Skipper = DefaultStaticConfig.Skipper
	}
	if config.Index == "" {
		config.Index = DefaultStaticConfig.Index
	}

	// Index template
	t, err := template.New("index").Parse(html)
	if err != nil {
		panic(fmt.Sprintf("echo: %v", err))
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) (err error) {
			if config.Skipper(c) {
				return next(c)
			}

			p := c.Request().URL.Path
			if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
				p = c.Param("*")
			}
			p, err = url.PathUnescape(p)
			if err != nil {
				return
			}
			name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security

			if config.IgnoreBase {
				routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
				baseURLPath := path.Base(p)
				if baseURLPath == routePath {
					i := strings.LastIndex(name, routePath)
					name = name[:i] + strings.Replace(name[i:], routePath, "", 1)
				}
			}

			fi, err := os.Stat(name)
			if err != nil {
				if os.IsNotExist(err) {
					if err = next(c); err != nil {
						if he, ok := err.(*echo.HTTPError); ok {
							if config.HTML5 && he.Code == http.StatusNotFound {
								return c.File(filepath.Join(config.Root, config.Index))
							}
						}
						return
					}
				}
				return
			}

			if fi.IsDir() {
				index := filepath.Join(name, config.Index)
				fi, err = os.Stat(index)

				if err != nil {
					if config.Browse {
						return listDir(t, name, c.Response())
					}
					if os.IsNotExist(err) {
						return next(c)
					}
					return
				}

				return c.File(index)
			}

			return c.File(name)
		}
	}
}

func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
	h := func(c Context) error {
		p, err := url.PathUnescape(c.Param("*"))
		if err != nil {
			return err
		}

		name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
		fi, err := os.Stat(name)
		if err != nil {
			// The access path does not exist
			return NotFoundHandler(c)
		}

		// If the request is for a directory and does not end with "/"
		p = c.Request().URL.Path // path must not be empty.
		if fi.IsDir() && p[len(p)-1] != '/' {
			// Redirect to ends with "/"
			return c.Redirect(http.StatusMovedPermanently, p+"/")
		}
		return c.File(name)
	}
	if prefix == "/" {
		return get(prefix+"*", h)
	}
	return get(prefix+"/*", h)
}

func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
	// Defaults
	if config.Root == "" {
		config.Root = "." // For security we want to restrict to CWD.
	}
	if config.Skipper == nil {
		config.Skipper = DefaultStaticConfig.Skipper
	}
	if config.Index == "" {
		config.Index = DefaultStaticConfig.Index
	}

	// Index template
	t, err := template.New("index").Parse(html)
	if err != nil {
		panic(fmt.Sprintf("echo: %v", err))
	}

	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) (err error) {
			if config.Skipper(c) {
				return next(c)
			}

			p := c.Request().URL.Path
			if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
				p = c.Param("*")
			}
			p, err = url.PathUnescape(p)
			if err != nil {
				return
			}
			name := filepath.Join(config.Root, filepath.Clean("/"+p)) // "/"+ for security

			if config.IgnoreBase {
				routePath := path.Base(strings.TrimRight(c.Path(), "/*"))
				baseURLPath := path.Base(p)
				if baseURLPath == routePath {
					i := strings.LastIndex(name, routePath)
					name = name[:i] + strings.Replace(name[i:], routePath, "", 1)
				}
			}

			fi, err := os.Stat(name)
			if err != nil {
				if os.IsNotExist(err) {
					if err = next(c); err != nil {
						if he, ok := err.(*echo.HTTPError); ok {
							if config.HTML5 && he.Code == http.StatusNotFound {
								return c.File(filepath.Join(config.Root, config.Index))
							}
						}
						return
					}
				}
				return
			}

			if fi.IsDir() {
				index := filepath.Join(name, config.Index)
				fi, err = os.Stat(index)

				if err != nil {
					if config.Browse {
						return listDir(t, name, c.Response())
					}
					if os.IsNotExist(err) {
						return next(c)
					}
					return
				}

				return c.File(index)
			}

			return c.File(name)
		}
	}
}

--- a/echo.go
+++ b/echo.go
@@ -5,7 +5,7 @@
 			return err
 		}

-		name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
+		name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
 		fi, err := os.Stat(name)
 		if err != nil {
 			// The access path does not exist

--- a/middleware/static.go
+++ b/middleware/static.go
@@ -30,7 +30,7 @@
 			if err != nil {
 				return
 			}
-			name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
+			name := filepath.Join(config.Root, filepath.Clean("/"+p)) // "/"+ for security

 			if config.IgnoreBase {
 				routePath := path.Base(strings.TrimRight(c.Path(), "/*"))


GitPython before 3.1.32 does not block insecure non-multi options in clone and clone_from. NOTE: this issue exists because of an incomplete fix for CVE-2022-24439.

    def _clone(
        cls,
        git: "Git",
        url: PathLike,
        path: PathLike,
        odb_default_type: Type[GitCmdObjectDB],
        progress: Union["RemoteProgress", "UpdateProgress", Callable[..., "RemoteProgress"], None] = None,
        multi_options: Optional[List[str]] = None,
        allow_unsafe_protocols: bool = False,
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> "Repo":
        odbt = kwargs.pop("odbt", odb_default_type)

        # when pathlib.Path or other classbased path is passed
        if not isinstance(path, str):
            path = str(path)

        ## A bug win cygwin's Git, when `--bare` or `--separate-git-dir`
        #  it prepends the cwd or(?) the `url` into the `path, so::
        #        git clone --bare  /cygwin/d/foo.git  C:\\Work
        #  becomes::
        #        git clone --bare  /cygwin/d/foo.git  /cygwin/d/C:\\Work
        #
        clone_path = Git.polish_url(path) if Git.is_cygwin() and "bare" in kwargs else path
        sep_dir = kwargs.get("separate_git_dir")
        if sep_dir:
            kwargs["separate_git_dir"] = Git.polish_url(sep_dir)
        multi = None
        if multi_options:
            multi = shlex.split(" ".join(multi_options))

        if not allow_unsafe_protocols:
            Git.check_unsafe_protocols(str(url))
        if not allow_unsafe_options and multi_options:
            Git.check_unsafe_options(options=multi_options, unsafe_options=cls.unsafe_git_clone_options)

        proc = git.clone(
            multi,
            "--",
            Git.polish_url(str(url)),
            clone_path,
            with_extended_output=True,
            as_process=True,
            v=True,
            universal_newlines=True,
            **add_progress(kwargs, git, progress),
        )
        if progress:
            handle_process_output(
                proc,
                None,
                to_progress_instance(progress).new_message_handler(),
                finalize_process,
                decode_streams=False,
            )
        else:
            (stdout, stderr) = proc.communicate()
            cmdline = getattr(proc, "args", "")
            cmdline = remove_password_if_present(cmdline)

            log.debug("Cmd(%s)'s unused stdout: %s", cmdline, stdout)
            finalize_process(proc, stderr=stderr)

        # our git command could have a different working dir than our actual
        # environment, hence we prepend its working dir if required
        if not osp.isabs(path):
            path = osp.join(git._working_dir, path) if git._working_dir is not None else path

        repo = cls(path, odbt=odbt)

        # retain env values that were passed to _clone()
        repo.git.update_environment(**git.environment())

        # adjust remotes - there may be operating systems which use backslashes,
        # These might be given as initial paths, but when handling the config file
        # that contains the remote from which we were clones, git stops liking it
        # as it will escape the backslashes. Hence we undo the escaping just to be
        # sure
        if repo.remotes:
            with repo.remotes[0].config_writer as writer:
                writer.set_value("url", Git.polish_url(repo.remotes[0].url))
        # END handle remote repo
        return repo

    def _clone(
        cls,
        git: "Git",
        url: PathLike,
        path: PathLike,
        odb_default_type: Type[GitCmdObjectDB],
        progress: Union["RemoteProgress", "UpdateProgress", Callable[..., "RemoteProgress"], None] = None,
        multi_options: Optional[List[str]] = None,
        allow_unsafe_protocols: bool = False,
        allow_unsafe_options: bool = False,
        **kwargs: Any,
    ) -> "Repo":
        odbt = kwargs.pop("odbt", odb_default_type)

        # when pathlib.Path or other classbased path is passed
        if not isinstance(path, str):
            path = str(path)

        ## A bug win cygwin's Git, when `--bare` or `--separate-git-dir`
        #  it prepends the cwd or(?) the `url` into the `path, so::
        #        git clone --bare  /cygwin/d/foo.git  C:\\Work
        #  becomes::
        #        git clone --bare  /cygwin/d/foo.git  /cygwin/d/C:\\Work
        #
        clone_path = Git.polish_url(path) if Git.is_cygwin() and "bare" in kwargs else path
        sep_dir = kwargs.get("separate_git_dir")
        if sep_dir:
            kwargs["separate_git_dir"] = Git.polish_url(sep_dir)
        multi = None
        if multi_options:
            multi = shlex.split(" ".join(multi_options))

        if not allow_unsafe_protocols:
            Git.check_unsafe_protocols(str(url))
        if not allow_unsafe_options:
            Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)
        if not allow_unsafe_options and multi_options:
            Git.check_unsafe_options(options=multi_options, unsafe_options=cls.unsafe_git_clone_options)

        proc = git.clone(
            multi,
            "--",
            Git.polish_url(str(url)),
            clone_path,
            with_extended_output=True,
            as_process=True,
            v=True,
            universal_newlines=True,
            **add_progress(kwargs, git, progress),
        )
        if progress:
            handle_process_output(
                proc,
                None,
                to_progress_instance(progress).new_message_handler(),
                finalize_process,
                decode_streams=False,
            )
        else:
            (stdout, stderr) = proc.communicate()
            cmdline = getattr(proc, "args", "")
            cmdline = remove_password_if_present(cmdline)

            log.debug("Cmd(%s)'s unused stdout: %s", cmdline, stdout)
            finalize_process(proc, stderr=stderr)

        # our git command could have a different working dir than our actual
        # environment, hence we prepend its working dir if required
        if not osp.isabs(path):
            path = osp.join(git._working_dir, path) if git._working_dir is not None else path

        repo = cls(path, odbt=odbt)

        # retain env values that were passed to _clone()
        repo.git.update_environment(**git.environment())

        # adjust remotes - there may be operating systems which use backslashes,
        # These might be given as initial paths, but when handling the config file
        # that contains the remote from which we were clones, git stops liking it
        # as it will escape the backslashes. Hence we undo the escaping just to be
        # sure
        if repo.remotes:
            with repo.remotes[0].config_writer as writer:
                writer.set_value("url", Git.polish_url(repo.remotes[0].url))
        # END handle remote repo
        return repo

--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -32,6 +32,8 @@

         if not allow_unsafe_protocols:
             Git.check_unsafe_protocols(str(url))
+        if not allow_unsafe_options:
+            Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)
         if not allow_unsafe_options and multi_options:
             Git.check_unsafe_options(options=multi_options, unsafe_options=cls.unsafe_git_clone_options)

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

cd /workspace/GitPython
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2023-40267/bin/python  -m pytest test/test_repo.py::TestRepo::test_clone_unsafe_options  test/test_repo.py::TestRepo::test_clone_from_unsafe_options -v

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

cd /workspace/GitPython
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2023-40267/bin/python  -m pytest test/test_repo.py -v -k "not test_archive and not test_clone_from_with_path_contains_unicode and not test_commit_from_revision and not test_commits and not test_creation_deletion and not test_do_not_strip_newline_in_stdout and not test_is_ancestor and not test_is_valid_object and not test_merge_base and not test_rebasing and not test_remote_method and not test_repo_creation_from_different_paths and not test_repo_creation_pathlib and not test_rev_parse and not test_submodule_update and not test_submodules and not test_tag and not test_tree_from_revision and not test_trees" -p no:warning --disable-warnings


Applio is a voice conversion tool. Versions 3.2.8-bugfix and prior are vulnerable to unsafe deserialization in inference.py. `model_file` in inference.py as well as `model_file` in tts.py take user-supplied input (e.g. a path to a model) and pass that value to the `change_choices` and later to `get_speakers_id` function, which loads that model with `torch.load` in inference.py (line 326 in 3.2.8-bugfix), which is vulnerable to unsafe deserialization. The issue can lead to remote code execution. A patch is available on the `main` branch of the repository.

Deserialization of Untrusted Data

The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.

def verify_checkpoint_shapes(checkpoint_path, model):
    checkpoint = torch.load(checkpoint_path, map_location="cpu")
    checkpoint_state_dict = checkpoint["model"]
    try:
        if hasattr(model, "module"):
            model_state_dict = model.module.load_state_dict(checkpoint_state_dict)
        else:
            model_state_dict = model.load_state_dict(checkpoint_state_dict)
    except RuntimeError:
        print(
            "The parameters of the pretrain model such as the sample rate or architecture do not match the selected model."
        )
        sys.exit(1)
    else:
        del checkpoint
        del checkpoint_state_dict
        del model_state_dict

def verify_checkpoint_shapes(checkpoint_path, model):
    checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True)
    checkpoint_state_dict = checkpoint["model"]
    try:
        if hasattr(model, "module"):
            model_state_dict = model.module.load_state_dict(checkpoint_state_dict)
        else:
            model_state_dict = model.load_state_dict(checkpoint_state_dict)
    except RuntimeError:
        print(
            "The parameters of the pretrain model such as the sample rate or architecture do not match the selected model."
        )
        sys.exit(1)
    else:
        del checkpoint
        del checkpoint_state_dict
        del model_state_dict

--- a/rvc/train/train.py
+++ b/rvc/train/train.py
@@ -1,5 +1,5 @@
 def verify_checkpoint_shapes(checkpoint_path, model):
-    checkpoint = torch.load(checkpoint_path, map_location="cpu")
+    checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True)
     checkpoint_state_dict = checkpoint["model"]
     try:
         if hasattr(model, "module"):