HashiCorp go-slug up to 0.4.3 did not fully protect against directory traversal while unpacking tar archives, and protections could be bypassed with specific constructions of multiple symlinks. Fixed in 0.5.0.

func Unpack(r io.Reader, dst string) error {
	// Decompress as we read.
	uncompressed, err := gzip.NewReader(r)
	if err != nil {
		return fmt.Errorf("Failed to uncompress slug: %v", err)
	}

	// Untar as we read.
	untar := tar.NewReader(uncompressed)

	// Unpackage all the contents into the directory.
	for {
		header, err := untar.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("Failed to untar slug: %v", err)
		}

		// Get rid of absolute paths.
		path := header.Name
		if path[0] == '/' {
			path = path[1:]
		}
		path = filepath.Join(dst, path)

		// Make the directories to the path.
		dir := filepath.Dir(path)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("Failed to create directory %q: %v", dir, err)
		}

		// Handle symlinks.
		if header.Typeflag == tar.TypeSymlink {
			// Disallow absolute targets.
			if filepath.IsAbs(header.Linkname) {
				return fmt.Errorf("Invalid symlink (%q -> %q) has absolute target",
					header.Name, header.Linkname)
			}

			// Ensure the link target is within the destination directory. This
			// disallows providing symlinks to external files and directories.
			target := filepath.Join(dir, header.Linkname)
			if !strings.HasPrefix(target, dst) {
				return fmt.Errorf("Invalid symlink (%q -> %q) has external target",
					header.Name, header.Linkname)
			}

			// Create the symlink.
			if err := os.Symlink(header.Linkname, path); err != nil {
				return fmt.Errorf("Failed creating symlink (%q -> %q): %v",
					header.Name, header.Linkname, err)
			}

			continue
		}

		// Only unpack regular files from this point on.
		if header.Typeflag == tar.TypeDir {
			continue
		} else if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA {
			return fmt.Errorf("Failed creating %q: unsupported type %c", path,
				header.Typeflag)
		}

		// Open a handle to the destination.
		fh, err := os.Create(path)
		if err != nil {
			// This mimics tar's behavior wrt the tar file containing duplicate files
			// and it allowing later ones to clobber earlier ones even if the file
			// has perms that don't allow overwriting.
			if os.IsPermission(err) {
				os.Chmod(path, 0600)
				fh, err = os.Create(path)
			}

			if err != nil {
				return fmt.Errorf("Failed creating file %q: %v", path, err)
			}
		}

		// Copy the contents.
		_, err = io.Copy(fh, untar)
		fh.Close()
		if err != nil {
			return fmt.Errorf("Failed to copy slug file %q: %v", path, err)
		}

		// Restore the file mode. We have to do this after writing the file,
		// since it is possible we have a read-only mode.
		mode := header.FileInfo().Mode()
		if err := os.Chmod(path, mode); err != nil {
			return fmt.Errorf("Failed setting permissions on %q: %v", path, err)
		}
	}
	return nil
}

func Unpack(r io.Reader, dst string) error {
	// Decompress as we read.
	uncompressed, err := gzip.NewReader(r)
	if err != nil {
		return fmt.Errorf("Failed to uncompress slug: %v", err)
	}

	// Untar as we read.
	untar := tar.NewReader(uncompressed)

	// Unpackage all the contents into the directory.
	for {
		header, err := untar.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return fmt.Errorf("Failed to untar slug: %v", err)
		}

		// Get rid of absolute paths.
		path := header.Name
		if path[0] == '/' {
			path = path[1:]
		}
		path = filepath.Join(dst, path)

		// Check for paths outside our directory, they are forbidden
		target := filepath.Clean(path)
		if !strings.HasPrefix(target, dst) {
			return fmt.Errorf("Invalid filename, traversal with \"..\" outside of current directory")
		}

		// Ensure the destination is not through any symlinks. This prevents
		// any files from being deployed through symlinks defined in the slug.
		// There are malicious cases where this could be used to escape the
		// slug's boundaries (zipslip), and any legitimate use is questionable
		// and likely indicates a hand-crafted tar file, which we are not in
		// the business of supporting here.
		//
		// The strategy is to Lstat each path  component from dst up to the
		// immediate parent directory of the file name in the tarball, checking
		// the mode on each to ensure we wouldn't be passing through any
		// symlinks.
		currentPath := dst // Start at the root of the unpacked tarball.
		components := strings.Split(header.Name, "/")

		for i := 0; i < len(components)-1; i++ {
			currentPath = filepath.Join(currentPath, components[i])
			fi, err := os.Lstat(currentPath)
			if os.IsNotExist(err) {
				// Parent directory structure is incomplete. Technically this
				// means from here upward cannot be a symlink, so we cancel the
				// remaining path tests.
				break
			}
			if err != nil {
				return fmt.Errorf("Failed to evaluate path %q: %v", header.Name, err)
			}
			if fi.Mode()&os.ModeSymlink != 0 {
				return fmt.Errorf("Cannot extract %q through symlink",
					header.Name)
			}
		}

		// Make the directories to the path.
		dir := filepath.Dir(path)
		if err := os.MkdirAll(dir, 0755); err != nil {
			return fmt.Errorf("Failed to create directory %q: %v", dir, err)
		}

		// Handle symlinks.
		if header.Typeflag == tar.TypeSymlink {
			// Disallow absolute targets.
			if filepath.IsAbs(header.Linkname) {
				return fmt.Errorf("Invalid symlink (%q -> %q) has absolute target",
					header.Name, header.Linkname)
			}

			// Ensure the link target is within the destination directory. This
			// disallows providing symlinks to external files and directories.
			target := filepath.Join(dir, header.Linkname)
			if !strings.HasPrefix(target, dst) {
				return fmt.Errorf("Invalid symlink (%q -> %q) has external target",
					header.Name, header.Linkname)
			}

			// Create the symlink.
			if err := os.Symlink(header.Linkname, path); err != nil {
				return fmt.Errorf("Failed creating symlink (%q -> %q): %v",
					header.Name, header.Linkname, err)
			}

			continue
		}

		// Only unpack regular files from this point on.
		if header.Typeflag == tar.TypeDir {
			continue
		} else if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA {
			return fmt.Errorf("Failed creating %q: unsupported type %c", path,
				header.Typeflag)
		}

		// Open a handle to the destination.
		fh, err := os.Create(path)
		if err != nil {
			// This mimics tar's behavior wrt the tar file containing duplicate files
			// and it allowing later ones to clobber earlier ones even if the file
			// has perms that don't allow overwriting.
			if os.IsPermission(err) {
				os.Chmod(path, 0600)
				fh, err = os.Create(path)
			}

			if err != nil {
				return fmt.Errorf("Failed creating file %q: %v", path, err)
			}
		}

		// Copy the contents.
		_, err = io.Copy(fh, untar)
		fh.Close()
		if err != nil {
			return fmt.Errorf("Failed to copy slug file %q: %v", path, err)
		}

		// Restore the file mode. We have to do this after writing the file,
		// since it is possible we have a read-only mode.
		mode := header.FileInfo().Mode()
		if err := os.Chmod(path, mode); err != nil {
			return fmt.Errorf("Failed setting permissions on %q: %v", path, err)
		}
	}
	return nil
}

--- a/slug.go
+++ b/slug.go
@@ -24,6 +24,44 @@
 			path = path[1:]
 		}
 		path = filepath.Join(dst, path)
+
+		// Check for paths outside our directory, they are forbidden
+		target := filepath.Clean(path)
+		if !strings.HasPrefix(target, dst) {
+			return fmt.Errorf("Invalid filename, traversal with \"..\" outside of current directory")
+		}
+
+		// Ensure the destination is not through any symlinks. This prevents
+		// any files from being deployed through symlinks defined in the slug.
+		// There are malicious cases where this could be used to escape the
+		// slug's boundaries (zipslip), and any legitimate use is questionable
+		// and likely indicates a hand-crafted tar file, which we are not in
+		// the business of supporting here.
+		//
+		// The strategy is to Lstat each path  component from dst up to the
+		// immediate parent directory of the file name in the tarball, checking
+		// the mode on each to ensure we wouldn't be passing through any
+		// symlinks.
+		currentPath := dst // Start at the root of the unpacked tarball.
+		components := strings.Split(header.Name, "/")
+
+		for i := 0; i < len(components)-1; i++ {
+			currentPath = filepath.Join(currentPath, components[i])
+			fi, err := os.Lstat(currentPath)
+			if os.IsNotExist(err) {
+				// Parent directory structure is incomplete. Technically this
+				// means from here upward cannot be a symlink, so we cancel the
+				// remaining path tests.
+				break
+			}
+			if err != nil {
+				return fmt.Errorf("Failed to evaluate path %q: %v", header.Name, err)
+			}
+			if fi.Mode()&os.ModeSymlink != 0 {
+				return fmt.Errorf("Cannot extract %q through symlink",
+					header.Name)
+			}
+		}

 		// Make the directories to the path.
 		dir := filepath.Dir(path)

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

cd /workspace/go-slug
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -timeout 30s -run ^TestUnpackMaliciousSymlinks$ github.com/hashicorp/go-slug

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

cd /workspace/go-slug
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestUnpackErrorOnUnhandledType|TestUnpackDuplicateNoWritePerm|TestPackWithoutDereferencing|TestUnpack|TestCheckFileMode|TestPackWithDereferencing|TestUnpackMaliciousSymlinks|TestPack)$' github.com/hashicorp/go-slug


powerline-gitstatus (aka Powerline Gitstatus) before 1.3.2 allows arbitrary code execution. git repositories can contain per-repository configuration that changes the behavior of git, including running arbitrary commands. When using powerline-gitstatus, changing to a directory automatically runs git commands in order to display information about the current repository in the prompt. If an attacker can convince a user to change their current directory to one controlled by the attacker, such as in a shared filesystem or extracted archive, powerline-gitstatus will run arbitrary commands under the attacker's control. NOTE: this is similar to CVE-2022-20001.

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.

    def get_base_command(self, cwd, use_dash_c):
        if use_dash_c:
            return ['git', '-C', cwd]

        while cwd and cwd != os.sep:
            gitdir = os.path.join(cwd, '.git')

            if os.path.isdir(gitdir):
                return ['git', '--git-dir=%s' % gitdir, '--work-tree=%s' % cwd]

            cwd = os.path.dirname(cwd)

        return None

    def get_base_command(self, cwd, use_dash_c):
        if use_dash_c:
            return ['git', '-c', 'core.fsmonitor=', '-C', cwd]

        while cwd and cwd != os.sep:
            gitdir = os.path.join(cwd, '.git')

            if os.path.isdir(gitdir):
                return ['git', '-c', 'core.fsmonitor=', '--git-dir=%s' % gitdir, '--work-tree=%s' % cwd]

            cwd = os.path.dirname(cwd)

        return None

--- a/powerline_gitstatus/segments.py
+++ b/powerline_gitstatus/segments.py
@@ -1,12 +1,12 @@
     def get_base_command(self, cwd, use_dash_c):
         if use_dash_c:
-            return ['git', '-C', cwd]
+            return ['git', '-c', 'core.fsmonitor=', '-C', cwd]

         while cwd and cwd != os.sep:
             gitdir = os.path.join(cwd, '.git')

             if os.path.isdir(gitdir):
-                return ['git', '--git-dir=%s' % gitdir, '--work-tree=%s' % cwd]
+                return ['git', '-c', 'core.fsmonitor=', '--git-dir=%s' % gitdir, '--work-tree=%s' % cwd]

             cwd = os.path.dirname(cwd)


Express.js minimalist web framework for node. Versions of Express.js prior to 4.19.0 and all pre-release alpha and beta versions of 5.0 are affected by an open redirect vulnerability using malformed URLs. When a user of Express performs a redirect using a user-provided URL Express performs an encode [using `encodeurl`](https://github.com/pillarjs/encodeurl) on the contents before passing it to the `location` header. This can cause malformed URLs to be evaluated in unexpected ways by common redirect allow list implementations in Express applications, leading to an Open Redirect via bypass of a properly implemented allow list. The main method impacted is `res.location()` but this is also called from within `res.redirect()`. The vulnerability is fixed in 4.19.2 and 5.0.0-beta.3.

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.

res.location = function location(url) {
  var loc = url;

  // "back" is an alias for the referrer
  if (url === 'back') {
    loc = this.req.get('Referrer') || '/';
  }

  // set location

res.location = function location(url) {
  var loc = url;

  // "back" is an alias for the referrer
  if (url === 'back') {
    loc = this.req.get('Referrer') || '/';
  }

  var lowerLoc = loc.toLowerCase();
  var encodedUrl = encodeUrl(loc);
  if (lowerLoc.indexOf('https://') === 0 || lowerLoc.indexOf('http://') === 0) {
    try {
      var parsedUrl = urlParse(loc);
      var parsedEncodedUrl = urlParse(encodedUrl);
      // Because this can encode the host, check that we did not change the host
      if (parsedUrl.host !== parsedEncodedUrl.host) {
        // If the host changes after encodeUrl, return the original url
        return this.set('Location', loc);
      }
    } catch (e) {
      // If parse fails, return the original url
      return this.set('Location', loc);
    }
  }

  // set location
  return this.set('Location', encodedUrl);
};

--- a/lib/response.js
+++ b/lib/response.js
@@ -6,4 +6,23 @@
     loc = this.req.get('Referrer') || '/';
   }

+  var lowerLoc = loc.toLowerCase();
+  var encodedUrl = encodeUrl(loc);
+  if (lowerLoc.indexOf('https://') === 0 || lowerLoc.indexOf('http://') === 0) {
+    try {
+      var parsedUrl = urlParse(loc);
+      var parsedEncodedUrl = urlParse(encodedUrl);
+      // Because this can encode the host, check that we did not change the host
+      if (parsedUrl.host !== parsedEncodedUrl.host) {
+        // If the host changes after encodeUrl, return the original url
+        return this.set('Location', loc);
+      }
+    } catch (e) {
+      // If parse fails, return the original url
+      return this.set('Location', loc);
+    }
+  }
+
   // set location
+  return this.set('Location', encodedUrl);
+};

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

cd /workspace/express
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --grep 'should not encode bad "url"'

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

cd /workspace/express
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha test/res.location.js


Django 1.10 before 1.10.7, 1.9 before 1.9.13, and 1.8 before 1.8.18 relies on user input in some cases to redirect the user to an "on success" URL. The security check for these redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric URLs "safe" when they shouldn't be, aka an open redirect vulnerability. Also, if a developer relies on ``is_safe_url()`` to provide safe redirect targets and puts such a URL into a link, they could suffer from an XSS attack.

def _is_safe_url(url, host):
    # Chrome considers any URL with more than two slashes to be absolute, but
    # urlparse is not so flexible. Treat any url with three slashes as unsafe.
    if url.startswith('///'):
        return False
    url_info = urlparse(url)
    # Forbid URLs like http:///example.com - with a scheme, but without a hostname.
    # In that URL, example.com is not the hostname but, a path component. However,
    # Chrome will still consider example.com to be the hostname, so we must not
    # allow this syntax.
    if not url_info.netloc and url_info.scheme:
        return False
    # Forbid URLs that start with control characters. Some browsers (like
    # Chrome) ignore quite a few control characters at the start of a
    # URL and might consider the URL as scheme relative.
    if unicodedata.category(url[0])[0] == 'C':
        return False
    return ((not url_info.netloc or url_info.netloc == host) and
            (not url_info.scheme or url_info.scheme in ['http', 'https']))

# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
def _urlparse(url, scheme='', allow_fragments=True):
    """Parse a URL into 6 components:
    <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
    Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""
    if _coerce_args:
        url, scheme, _coerce_result = _coerce_args(url, scheme)
    splitresult = _urlsplit(url, scheme, allow_fragments)
    scheme, netloc, url, query, fragment = splitresult
    if scheme in uses_params and ';' in url:
        url, params = _splitparams(url)
    else:
        params = ''
    result = ParseResult(scheme, netloc, url, params, query, fragment)
    return _coerce_result(result) if _coerce_args else result

# Copied from urllib.parse.urlsplit() with
# https://github.com/python/cpython/pull/661 applied.
def _urlsplit(url, scheme='', allow_fragments=True):
    """Parse a URL into 5 components:
    <scheme>://<netloc>/<path>?<query>#<fragment>
    Return a 5-tuple: (scheme, netloc, path, query, fragment).
    Note that we don't break the components up in smaller bits
    (e.g. netloc is a single string) and we don't expand % escapes."""
    if _coerce_args:
        url, scheme, _coerce_result = _coerce_args(url, scheme)
    allow_fragments = bool(allow_fragments)
    netloc = query = fragment = ''
    i = url.find(':')
    if i > 0:
        for c in url[:i]:
            if c not in scheme_chars:
                break
        else:
            scheme, url = url[:i].lower(), url[i + 1:]

    if url[:2] == '//':
        netloc, url = _splitnetloc(url, 2)
        if (('[' in netloc and ']' not in netloc) or
                (']' in netloc and '[' not in netloc)):
            raise ValueError("Invalid IPv6 URL")
    if allow_fragments and '#' in url:
        url, fragment = url.split('#', 1)
    if '?' in url:
        url, query = url.split('?', 1)
    v = SplitResult(scheme, netloc, url, query, fragment)
    return _coerce_result(v) if _coerce_args else v

def _is_safe_url(url, host):
    # Chrome considers any URL with more than two slashes to be absolute, but
    # urlparse is not so flexible. Treat any url with three slashes as unsafe.
    if url.startswith('///'):
        return False
    url_info = _urlparse(url)
    # Forbid URLs like http:///example.com - with a scheme, but without a hostname.
    # In that URL, example.com is not the hostname but, a path component. However,
    # Chrome will still consider example.com to be the hostname, so we must not
    # allow this syntax.
    if not url_info.netloc and url_info.scheme:
        return False
    # Forbid URLs that start with control characters. Some browsers (like
    # Chrome) ignore quite a few control characters at the start of a
    # URL and might consider the URL as scheme relative.
    if unicodedata.category(url[0])[0] == 'C':
        return False
    return ((not url_info.netloc or url_info.netloc == host) and
            (not url_info.scheme or url_info.scheme in ['http', 'https']))

--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -1,9 +1,61 @@
+# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
+def _urlparse(url, scheme='', allow_fragments=True):
+    """Parse a URL into 6 components:
+    <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
+    Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
+    Note that we don't break the components up in smaller bits
+    (e.g. netloc is a single string) and we don't expand % escapes."""
+    if _coerce_args:
+        url, scheme, _coerce_result = _coerce_args(url, scheme)
+    splitresult = _urlsplit(url, scheme, allow_fragments)
+    scheme, netloc, url, query, fragment = splitresult
+    if scheme in uses_params and ';' in url:
+        url, params = _splitparams(url)
+    else:
+        params = ''
+    result = ParseResult(scheme, netloc, url, params, query, fragment)
+    return _coerce_result(result) if _coerce_args else result
+
+
+# Copied from urllib.parse.urlsplit() with
+# https://github.com/python/cpython/pull/661 applied.
+def _urlsplit(url, scheme='', allow_fragments=True):
+    """Parse a URL into 5 components:
+    <scheme>://<netloc>/<path>?<query>#<fragment>
+    Return a 5-tuple: (scheme, netloc, path, query, fragment).
+    Note that we don't break the components up in smaller bits
+    (e.g. netloc is a single string) and we don't expand % escapes."""
+    if _coerce_args:
+        url, scheme, _coerce_result = _coerce_args(url, scheme)
+    allow_fragments = bool(allow_fragments)
+    netloc = query = fragment = ''
+    i = url.find(':')
+    if i > 0:
+        for c in url[:i]:
+            if c not in scheme_chars:
+                break
+        else:
+            scheme, url = url[:i].lower(), url[i + 1:]
+
+    if url[:2] == '//':
+        netloc, url = _splitnetloc(url, 2)
+        if (('[' in netloc and ']' not in netloc) or
+                (']' in netloc and '[' not in netloc)):
+            raise ValueError("Invalid IPv6 URL")
+    if allow_fragments and '#' in url:
+        url, fragment = url.split('#', 1)
+    if '?' in url:
+        url, query = url.split('?', 1)
+    v = SplitResult(scheme, netloc, url, query, fragment)
+    return _coerce_result(v) if _coerce_args else v
+
+
 def _is_safe_url(url, host):
     # Chrome considers any URL with more than two slashes to be absolute, but
     # urlparse is not so flexible. Treat any url with three slashes as unsafe.
     if url.startswith('///'):
         return False
-    url_info = urlparse(url)
+    url_info = _urlparse(url)
     # Forbid URLs like http:///example.com - with a scheme, but without a hostname.
     # In that URL, example.com is not the hostname but, a path component. However,
     # Chrome will still consider example.com to be the hostname, so we must not

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

cd /workspace/django
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
cd tests && /workspace/PoC_env/CVE-2017-7233/bin/python ./runtests.py utils_tests.test_http.TestUtilsHttp.test_is_safe_url

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

cd /workspace/django
git apply --whitespace=nowarn /workspace/fix.patch
cd tests && /workspace/PoC_env/CVE-2017-7233/bin/python ./runtests.py utils_tests.test_http


graphql-go is a GraphQL server with a focus on ease of use. In versions prior to 1.3.0 there exists a DoS vulnerability that is possible due to a bug in the library that would allow an attacker with specifically designed queries to cause stack overflow panics. Any user with access to the GraphQL handler can send these queries and cause stack overflows. This in turn could potentially compromise the ability of the server to serve data to its users. The issue has been patched in version `v1.3.0`. The only known workaround for this issue is to disable the `graphql.MaxDepth` option from your schema which is not recommended.

Uncontrolled Recursion

The product does not properly control the amount of recursion that takes place,  consuming excessive resources, such as allocated memory or the program stack.

func validateMaxDepth(c *opContext, sels []types.Selection, depth int) bool {
	// maxDepth checking is turned off when maxDepth is 0
	if c.maxDepth == 0 {
		return false
	}

	exceededMaxDepth := false

	for _, sel := range sels {
		switch sel := sel.(type) {
		case *types.Field:
			if depth > c.maxDepth {
				exceededMaxDepth = true
				c.addErr(sel.Alias.Loc, "MaxDepthExceeded", "Field %q has depth %d that exceeds max depth %d", sel.Name.Name, depth, c.maxDepth)
				continue
			}
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, depth+1)
		case *types.InlineFragment:
			// Depth is not checked because inline fragments resolve to other fields which are checked.
			// Depth is not incremented because inline fragments have the same depth as neighboring fields
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, depth)
		case *types.FragmentSpread:
			// Depth is not checked because fragments resolve to other fields which are checked.
			frag := c.doc.Fragments.Get(sel.Name.Name)
			if frag == nil {
				// In case of unknown fragment (invalid request), ignore max depth evaluation
				c.addErr(sel.Loc, "MaxDepthEvaluationError", "Unknown fragment %q. Unable to evaluate depth.", sel.Name.Name)
				continue
			}
			// Depth is not incremented because fragments have the same depth as surrounding fields
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, depth)
		}
	}

	return exceededMaxDepth
}

func validateMaxDepth(c *opContext, sels []types.Selection, visited map[*types.FragmentDefinition]struct{}, depth int) bool {
	// maxDepth checking is turned off when maxDepth is 0
	if c.maxDepth == 0 {
		return false
	}

	exceededMaxDepth := false
	if visited == nil {
		visited = map[*types.FragmentDefinition]struct{}{}
	}

	for _, sel := range sels {
		switch sel := sel.(type) {
		case *types.Field:
			if depth > c.maxDepth {
				exceededMaxDepth = true
				c.addErr(sel.Alias.Loc, "MaxDepthExceeded", "Field %q has depth %d that exceeds max depth %d", sel.Name.Name, depth, c.maxDepth)
				continue
			}
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, visited, depth+1)

		case *types.InlineFragment:
			// Depth is not checked because inline fragments resolve to other fields which are checked.
			// Depth is not incremented because inline fragments have the same depth as neighboring fields
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, visited, depth)
		case *types.FragmentSpread:
			// Depth is not checked because fragments resolve to other fields which are checked.
			frag := c.doc.Fragments.Get(sel.Name.Name)
			if frag == nil {
				// In case of unknown fragment (invalid request), ignore max depth evaluation
				c.addErr(sel.Loc, "MaxDepthEvaluationError", "Unknown fragment %q. Unable to evaluate depth.", sel.Name.Name)
				continue
			}

			if _, ok := visited[frag]; ok {
				// we've already seen this fragment, don't check depth again.
				continue
			}
			visited[frag] = struct{}{}

			// Depth is not incremented because fragments have the same depth as surrounding fields
			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, visited, depth)
		}
	}

	return exceededMaxDepth
}

--- a/internal/validation/validation.go
+++ b/internal/validation/validation.go
@@ -1,10 +1,13 @@
-func validateMaxDepth(c *opContext, sels []types.Selection, depth int) bool {
+func validateMaxDepth(c *opContext, sels []types.Selection, visited map[*types.FragmentDefinition]struct{}, depth int) bool {
 	// maxDepth checking is turned off when maxDepth is 0
 	if c.maxDepth == 0 {
 		return false
 	}

 	exceededMaxDepth := false
+	if visited == nil {
+		visited = map[*types.FragmentDefinition]struct{}{}
+	}

 	for _, sel := range sels {
 		switch sel := sel.(type) {
@@ -14,11 +17,12 @@
 				c.addErr(sel.Alias.Loc, "MaxDepthExceeded", "Field %q has depth %d that exceeds max depth %d", sel.Name.Name, depth, c.maxDepth)
 				continue
 			}
-			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, depth+1)
+			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.SelectionSet, visited, depth+1)
+
 		case *types.InlineFragment:
 			// Depth is not checked because inline fragments resolve to other fields which are checked.
 			// Depth is not incremented because inline fragments have the same depth as neighboring fields
-			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, depth)
+			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, sel.Selections, visited, depth)
 		case *types.FragmentSpread:
 			// Depth is not checked because fragments resolve to other fields which are checked.
 			frag := c.doc.Fragments.Get(sel.Name.Name)
@@ -27,8 +31,15 @@
 				c.addErr(sel.Loc, "MaxDepthEvaluationError", "Unknown fragment %q. Unable to evaluate depth.", sel.Name.Name)
 				continue
 			}
+
+			if _, ok := visited[frag]; ok {
+				// we've already seen this fragment, don't check depth again.
+				continue
+			}
+			visited[frag] = struct{}{}
+
 			// Depth is not incremented because fragments have the same depth as surrounding fields
-			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, depth)
+			exceededMaxDepth = exceededMaxDepth || validateMaxDepth(c, frag.Selections, visited, depth)
 		}
 	}


The Hashicorp go-getter library before 1.5.11 does not redact an SSH key from a URL query parameter.

Insertion of Sensitive Information into Log File

The product writes sensitive information to a log file.

func RedactURL(u *url.URL) string {
	if u == nil {
		return ""
	}

	ru := *u
	if _, has := ru.User.Password(); has {
		ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
	}
	return ru.String()
}

func RedactURL(u *url.URL) string {
	if u == nil {
		return ""
	}

	ru := *u
	if _, has := ru.User.Password(); has {
		ru.User = url.UserPassword(ru.User.Username(), "redacted")
	}
	q := ru.Query()
	if q.Get("sshkey") != "" {
		q.Set("sshkey", "redacted")
		ru.RawQuery = q.Encode()
	}
	return ru.String()
}

--- a/url.go
+++ b/url.go
@@ -5,7 +5,12 @@

 	ru := *u
 	if _, has := ru.User.Password(); has {
-		ru.User = url.UserPassword(ru.User.Username(), "xxxxx")
+		ru.User = url.UserPassword(ru.User.Username(), "redacted")
+	}
+	q := ru.Query()
+	if q.Get("sshkey") != "" {
+		q.Set("sshkey", "redacted")
+		ru.RawQuery = q.Encode()
 	}
 	return ru.String()
 }


Fiber is an express inspired web framework written in Go. A Cross-Site Request Forgery (CSRF) vulnerability has been identified in the application, which allows an attacker to inject arbitrary values and forge malicious requests on behalf of a user. This vulnerability can allow an attacker to inject arbitrary values without any authentication, or perform various malicious actions on behalf of an authenticated user, potentially compromising the security and integrity of the application. The vulnerability is caused by improper validation and enforcement of CSRF tokens within the application. This issue has been addressed in version 2.50.0 and users are advised to upgrade. Users should take additional security measures like captchas or Two-Factor Authentication (2FA) and set Session cookies with SameSite=Lax or SameSite=Secure, and the Secure and HttpOnly attributes as defense in depth measures. There are no known workarounds for this vulnerability.

Cross-Site Request Forgery (CSRF)

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

func New(config ...Config) fiber.Handler {
	// Set default config
	cfg := configDefault(config...)

	// Create manager to simplify storage operations ( see *_manager.go )
	var sessionManager *sessionManager
	var storageManager *storageManager
	if cfg.Session != nil {
		// Register the Token struct in the session store
		cfg.Session.RegisterType(Token{})

		sessionManager = newSessionManager(cfg.Session, cfg.SessionKey)
	} else {
		storageManager = newStorageManager(cfg.Storage)
	}

	// Return new handler
	return func(c *fiber.Ctx) error {
		// Don't execute middleware if Next returns true
		if cfg.Next != nil && cfg.Next(c) {
			return c.Next()
		}

		// Store the CSRF handler in the context if a context key is specified
		if cfg.HandlerContextKey != "" {
			c.Locals(cfg.HandlerContextKey, &CSRFHandler{
				config:         &cfg,
				sessionManager: sessionManager,
				storageManager: storageManager,
			})
		}

		var token string

		// Action depends on the HTTP method
		switch c.Method() {
		case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace:
			cookieToken := c.Cookies(cfg.CookieName)

			if cookieToken != "" {
				rawToken := getTokenFromStorage(c, cookieToken, cfg, sessionManager, storageManager)

				if rawToken != nil {
					token = string(rawToken)
				}
			}
		default:
			// Assume that anything not defined as 'safe' by RFC7231 needs protection

			// Enforce an origin check for HTTPS connections.
			if c.Protocol() == "https" {
				if err := refererMatchesHost(c); err != nil {
					return cfg.ErrorHandler(c, err)
				}
			}

			// Extract token from client request i.e. header, query, param, form or cookie
			extractedToken, err := cfg.Extractor(c)
			if err != nil {
				return cfg.ErrorHandler(c, err)
			}

			if extractedToken == "" {
				return cfg.ErrorHandler(c, ErrTokenNotFound)
			}

			// If not using CsrfFromCookie extractor, check that the token matches the cookie
			// This is to prevent CSRF attacks by using a Double Submit Cookie method
			// Useful when we do not have access to the users Session
			if !isCsrfFromCookie(cfg.Extractor) && extractedToken != c.Cookies(cfg.CookieName) {
				return cfg.ErrorHandler(c, ErrTokenInvalid)
			}

			rawToken := getTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)

			if rawToken == nil {
				// If token is not in storage, expire the cookie
				expireCSRFCookie(c, cfg)
				// and return an error
				return cfg.ErrorHandler(c, ErrTokenNotFound)
			}
			if cfg.SingleUseToken {
				// If token is single use, delete it from storage
				deleteTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
			} else {
				token = string(rawToken)
			}
		}

		// Generate CSRF token if not exist
		if token == "" {
			// And generate a new token
			token = cfg.KeyGenerator()
		}

		// Create or extend the token in the storage
		createOrExtendTokenInStorage(c, token, cfg, sessionManager, storageManager)

		// Update the CSRF cookie
		updateCSRFCookie(c, cfg, token)

		// Tell the browser that a new header value is generated
		c.Vary(fiber.HeaderCookie)

		// Store the token in the context if a context key is specified
		if cfg.ContextKey != "" {
			c.Locals(cfg.ContextKey, token)
		}

		// Continue stack
		return c.Next()
	}
}

func getTokenFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
	if cfg.Session != nil {
		return sessionManager.getRaw(c, token, dummyValue)
	}
	return storageManager.getRaw(token)
}

func New(config ...Config) fiber.Handler {
	// Set default config
	cfg := configDefault(config...)

	// Create manager to simplify storage operations ( see *_manager.go )
	var sessionManager *sessionManager
	var storageManager *storageManager
	if cfg.Session != nil {
		// Register the Token struct in the session store
		cfg.Session.RegisterType(Token{})

		sessionManager = newSessionManager(cfg.Session, cfg.SessionKey)
	} else {
		storageManager = newStorageManager(cfg.Storage)
	}

	// Return new handler
	return func(c *fiber.Ctx) error {
		// Don't execute middleware if Next returns true
		if cfg.Next != nil && cfg.Next(c) {
			return c.Next()
		}

		// Store the CSRF handler in the context if a context key is specified
		if cfg.HandlerContextKey != "" {
			c.Locals(cfg.HandlerContextKey, &CSRFHandler{
				config:         &cfg,
				sessionManager: sessionManager,
				storageManager: storageManager,
			})
		}

		var token string

		// Action depends on the HTTP method
		switch c.Method() {
		case fiber.MethodGet, fiber.MethodHead, fiber.MethodOptions, fiber.MethodTrace:
			cookieToken := c.Cookies(cfg.CookieName)

			if cookieToken != "" {
				raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)

				if raw != nil {
					token = cookieToken // Token is valid, safe to set it
				}
			}
		default:
			// Assume that anything not defined as 'safe' by RFC7231 needs protection

			// Enforce an origin check for HTTPS connections.
			if c.Protocol() == "https" {
				if err := refererMatchesHost(c); err != nil {
					return cfg.ErrorHandler(c, err)
				}
			}

			// Extract token from client request i.e. header, query, param, form or cookie
			extractedToken, err := cfg.Extractor(c)
			if err != nil {
				return cfg.ErrorHandler(c, err)
			}

			if extractedToken == "" {
				return cfg.ErrorHandler(c, ErrTokenNotFound)
			}

			// If not using CsrfFromCookie extractor, check that the token matches the cookie
			// This is to prevent CSRF attacks by using a Double Submit Cookie method
			// Useful when we do not have access to the users Session
			if !isCsrfFromCookie(cfg.Extractor) && !compareStrings(extractedToken, c.Cookies(cfg.CookieName)) {
				return cfg.ErrorHandler(c, ErrTokenInvalid)
			}

			raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)

			if raw == nil {
				// If token is not in storage, expire the cookie
				expireCSRFCookie(c, cfg)
				// and return an error
				return cfg.ErrorHandler(c, ErrTokenNotFound)
			}
			if cfg.SingleUseToken {
				// If token is single use, delete it from storage
				deleteTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
			} else {
				token = extractedToken // Token is valid, safe to set it
			}
		}

		// Generate CSRF token if not exist
		if token == "" {
			// And generate a new token
			token = cfg.KeyGenerator()
		}

		// Create or extend the token in the storage
		createOrExtendTokenInStorage(c, token, cfg, sessionManager, storageManager)

		// Update the CSRF cookie
		updateCSRFCookie(c, cfg, token)

		// Tell the browser that a new header value is generated
		c.Vary(fiber.HeaderCookie)

		// Store the token in the context if a context key is specified
		if cfg.ContextKey != "" {
			c.Locals(cfg.ContextKey, token)
		}

		// Continue stack
		return c.Next()
	}
}

func getRawFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
	if cfg.Session != nil {
		return sessionManager.getRaw(c, token, dummyValue)
	}
	return storageManager.getRaw(token)
}

func refererMatchesHost(c *fiber.Ctx) error {
	referer := c.Get(fiber.HeaderReferer)
	if referer == "" {
		return ErrNoReferer
	}

	refererURL, err := url.Parse(referer)
	if err != nil {
		return ErrBadReferer
	}

	if refererURL.Scheme+"://"+refererURL.Host != c.Protocol()+"://"+c.Hostname() {
		return ErrBadReferer
	}

	return nil
}

func compareStrings(a, b string) bool {
	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

--- a/middleware/csrf/csrf.go
+++ b/middleware/csrf/csrf.go
@@ -38,10 +38,10 @@
 			cookieToken := c.Cookies(cfg.CookieName)

 			if cookieToken != "" {
-				rawToken := getTokenFromStorage(c, cookieToken, cfg, sessionManager, storageManager)
+				raw := getRawFromStorage(c, cookieToken, cfg, sessionManager, storageManager)

-				if rawToken != nil {
-					token = string(rawToken)
+				if raw != nil {
+					token = cookieToken // Token is valid, safe to set it
 				}
 			}
 		default:
@@ -67,13 +67,13 @@
 			// If not using CsrfFromCookie extractor, check that the token matches the cookie
 			// This is to prevent CSRF attacks by using a Double Submit Cookie method
 			// Useful when we do not have access to the users Session
-			if !isCsrfFromCookie(cfg.Extractor) && extractedToken != c.Cookies(cfg.CookieName) {
+			if !isCsrfFromCookie(cfg.Extractor) && !compareStrings(extractedToken, c.Cookies(cfg.CookieName)) {
 				return cfg.ErrorHandler(c, ErrTokenInvalid)
 			}

-			rawToken := getTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
+			raw := getRawFromStorage(c, extractedToken, cfg, sessionManager, storageManager)

-			if rawToken == nil {
+			if raw == nil {
 				// If token is not in storage, expire the cookie
 				expireCSRFCookie(c, cfg)
 				// and return an error
@@ -83,7 +83,7 @@
 				// If token is single use, delete it from storage
 				deleteTokenFromStorage(c, extractedToken, cfg, sessionManager, storageManager)
 			} else {
-				token = string(rawToken)
+				token = extractedToken // Token is valid, safe to set it
 			}
 		}

--- a/middleware/csrf/csrf.go
+++ b/middleware/csrf/csrf.go
@@ -1,4 +1,4 @@
-func getTokenFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
+func getRawFromStorage(c *fiber.Ctx, token string, cfg Config, sessionManager *sessionManager, storageManager *storageManager) []byte {
 	if cfg.Session != nil {
 		return sessionManager.getRaw(c, token, dummyValue)
 	}

--- /dev/null
+++ b/middleware/csrf/csrf.go
@@ -0,0 +1,17 @@
+func refererMatchesHost(c *fiber.Ctx) error {
+	referer := c.Get(fiber.HeaderReferer)
+	if referer == "" {
+		return ErrNoReferer
+	}
+
+	refererURL, err := url.Parse(referer)
+	if err != nil {
+		return ErrBadReferer
+	}
+
+	if refererURL.Scheme+"://"+refererURL.Host != c.Protocol()+"://"+c.Hostname() {
+		return ErrBadReferer
+	}
+
+	return nil
+}

--- /dev/null
+++ b/middleware/csrf/csrf.go
@@ -0,0 +1,3 @@
+func compareStrings(a, b string) bool {
+	return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
+}

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

cd /workspace/fiber
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^Test_CSRF_ExpiredToken$ github.com/gofiber/fiber/v2/middleware/csrf


Wagtail is a Django based content management system focused on flexibility and user experience. When notifications for new replies in comment threads are sent, they are sent to all users who have replied or commented anywhere on the site, rather than only in the relevant threads. This means that a user could listen in to new comment replies on pages they have not have editing access to, as long as they have left a comment or reply somewhere on the site. A patched version has been released as Wagtail 2.15.2, which restores the intended behaviour - to send notifications for new replies to the participants in the active thread only (editing permissions are not considered). New comments can be disabled by setting `WAGTAILADMIN_COMMENTS_ENABLED = False` in the Django settings file.

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.

    def send_commenting_notifications(self, changes):
        """
        Sends notifications about any changes to comments to anyone who is subscribed.
        """
        relevant_comment_ids = []
        relevant_comment_ids.extend(comment.pk for comment in changes['resolved_comments'])
        relevant_comment_ids.extend(comment.pk for comment, replies in changes['new_replies'])

        # Skip if no changes were made
        # Note: We don't email about edited comments so ignore those here
        if (not changes['new_comments']
                and not changes['deleted_comments']
                and not changes['resolved_comments']
                and not changes['new_replies']):
            return

        # Get global page comment subscribers
        subscribers = PageSubscription.objects.filter(page=self.page, comment_notifications=True).select_related('user')
        global_recipient_users = [subscriber.user for subscriber in subscribers if subscriber.user != self.request.user]

        # Get subscribers to individual threads
        replies = CommentReply.objects.filter(comment_id__in=relevant_comment_ids)
        comments = Comment.objects.filter(id__in=relevant_comment_ids)
        thread_users = get_user_model().objects.exclude(pk=self.request.user.pk).exclude(pk__in=subscribers.values_list('user_id', flat=True)).prefetch_related(
            Prefetch('comment_replies', queryset=replies),
            Prefetch(COMMENTS_RELATION_NAME, queryset=comments)
        ).exclude(
            Q(comment_replies__isnull=True) & Q(**{('%s__isnull' % COMMENTS_RELATION_NAME): True})
        )

        # Skip if no recipients
        if not (global_recipient_users or thread_users):
            return
        thread_users = [(user, set(list(user.comment_replies.values_list('comment_id', flat=True)) + list(getattr(user, COMMENTS_RELATION_NAME).values_list('pk', flat=True)))) for user in thread_users]
        mailed_users = set()

        for current_user, current_threads in thread_users:
            # We are trying to avoid calling send_notification for each user for performance reasons
            # so group the users receiving the same thread notifications together here
            if current_user in mailed_users:
                continue
            users = [current_user]
            mailed_users.add(current_user)
            for user, threads in thread_users:
                if user not in mailed_users and threads == current_threads:
                    users.append(user)
                    mailed_users.add(user)
            send_notification(users, 'updated_comments', {
                'page': self.page,
                'editor': self.request.user,
                'new_comments': [comment for comment in changes['new_comments'] if comment.pk in threads],
                'resolved_comments': [comment for comment in changes['resolved_comments'] if comment.pk in threads],
                'deleted_comments': [],
                'replied_comments': [
                    {
                        'comment': comment,
                        'replies': replies,
                    }
                    for comment, replies in changes['new_replies']
                    if comment.pk in threads
                ]
            })

        return send_notification(global_recipient_users, 'updated_comments', {
            'page': self.page,
            'editor': self.request.user,
            'new_comments': changes['new_comments'],
            'resolved_comments': changes['resolved_comments'],
            'deleted_comments': changes['deleted_comments'],
            'replied_comments': [
                {
                    'comment': comment,
                    'replies': replies,
                }
                for comment, replies in changes['new_replies']
            ]
        })

    def send_commenting_notifications(self, changes):
        """
        Sends notifications about any changes to comments to anyone who is subscribed.
        """
        relevant_comment_ids = []
        relevant_comment_ids.extend(comment.pk for comment in changes['resolved_comments'])
        relevant_comment_ids.extend(comment.pk for comment, replies in changes['new_replies'])

        # Skip if no changes were made
        # Note: We don't email about edited comments so ignore those here
        if (not changes['new_comments']
                and not changes['deleted_comments']
                and not changes['resolved_comments']
                and not changes['new_replies']):
            return

        # Get global page comment subscribers
        subscribers = PageSubscription.objects.filter(page=self.page, comment_notifications=True).select_related('user')
        global_recipient_users = [subscriber.user for subscriber in subscribers if subscriber.user != self.request.user]

        # Get subscribers to individual threads
        replies = CommentReply.objects.filter(comment_id__in=relevant_comment_ids)
        comments = Comment.objects.filter(id__in=relevant_comment_ids)
        thread_users = get_user_model().objects.exclude(pk=self.request.user.pk).exclude(pk__in=subscribers.values_list('user_id', flat=True)).filter(
            Q(comment_replies__comment_id__in=relevant_comment_ids) | Q(**{('%s__pk__in' % COMMENTS_RELATION_NAME): relevant_comment_ids})
        ).prefetch_related(
            Prefetch('comment_replies', queryset=replies),
            Prefetch(COMMENTS_RELATION_NAME, queryset=comments)
        )

        # Skip if no recipients
        if not (global_recipient_users or thread_users):
            return
        thread_users = [(user, set(list(user.comment_replies.values_list('comment_id', flat=True)) + list(getattr(user, COMMENTS_RELATION_NAME).values_list('pk', flat=True)))) for user in thread_users]
        mailed_users = set()

        for current_user, current_threads in thread_users:
            # We are trying to avoid calling send_notification for each user for performance reasons
            # so group the users receiving the same thread notifications together here
            if current_user in mailed_users:
                continue
            users = [current_user]
            mailed_users.add(current_user)
            for user, threads in thread_users:
                if user not in mailed_users and threads == current_threads:
                    users.append(user)
                    mailed_users.add(user)
            send_notification(users, 'updated_comments', {
                'page': self.page,
                'editor': self.request.user,
                'new_comments': [comment for comment in changes['new_comments'] if comment.pk in threads],
                'resolved_comments': [comment for comment in changes['resolved_comments'] if comment.pk in threads],
                'deleted_comments': [],
                'replied_comments': [
                    {
                        'comment': comment,
                        'replies': replies,
                    }
                    for comment, replies in changes['new_replies']
                    if comment.pk in threads
                ]
            })

        return send_notification(global_recipient_users, 'updated_comments', {
            'page': self.page,
            'editor': self.request.user,
            'new_comments': changes['new_comments'],
            'resolved_comments': changes['resolved_comments'],
            'deleted_comments': changes['deleted_comments'],
            'replied_comments': [
                {
                    'comment': comment,
                    'replies': replies,
                }
                for comment, replies in changes['new_replies']
            ]
        })

--- a/wagtail/admin/views/pages/edit.py
+++ b/wagtail/admin/views/pages/edit.py
@@ -21,11 +21,11 @@
         # Get subscribers to individual threads
         replies = CommentReply.objects.filter(comment_id__in=relevant_comment_ids)
         comments = Comment.objects.filter(id__in=relevant_comment_ids)
-        thread_users = get_user_model().objects.exclude(pk=self.request.user.pk).exclude(pk__in=subscribers.values_list('user_id', flat=True)).prefetch_related(
+        thread_users = get_user_model().objects.exclude(pk=self.request.user.pk).exclude(pk__in=subscribers.values_list('user_id', flat=True)).filter(
+            Q(comment_replies__comment_id__in=relevant_comment_ids) | Q(**{('%s__pk__in' % COMMENTS_RELATION_NAME): relevant_comment_ids})
+        ).prefetch_related(
             Prefetch('comment_replies', queryset=replies),
             Prefetch(COMMENTS_RELATION_NAME, queryset=comments)
-        ).exclude(
-            Q(comment_replies__isnull=True) & Q(**{('%s__isnull' % COMMENTS_RELATION_NAME): True})
         )

         # Skip if no recipients

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

cd /workspace/wagtail
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2022-21683/bin/python ./wagtail/tests/manage.py test wagtail.admin.tests.pages.test_edit_page.TestCommenting.assertNeverEmailedWrongUser wagtail.admin.tests.pages.test_edit_page.TestCommenting.test_new_comment

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

cd /workspace/wagtail
git apply --whitespace=nowarn /workspace/fix.patch /workspace/dep.patch
/workspace/PoC_env/CVE-2022-21683/bin/python ./wagtail/tests/manage.py test wagtail.admin.tests.pages.test_edit_page


Improper Neutralization of Special Elements used in a Command ('Command Injection') vulnerability in Netflix ConsoleMe allows Command Injection.This issue affects ConsoleMe: before 1.4.0.

async def generate_honeybee_request_from_change_model_array(
    request_creation: RequestCreationModel, user: str, extended_request_uuid: str
) -> ExtendedRequestModel:
    repositories_for_request = {}
    primary_principal = None
    t = int(time.time())
    generated_branch_name = f"{user}-{t}"
    policy_name = config.get(
        "generate_honeybee_request_from_change_model_array.policy_name",
        "self_service_generated",
    )
    repo_config = None

    # Checkout Git Repo and generate a branch name for the user's change
    for change in request_creation.changes.changes:
        if primary_principal and change.principal != primary_principal:
            raise Exception("Changes must all affect the same principal")
        primary_principal = change.principal
        discovered_repository_for_change = False
        if repositories_for_request.get(change.principal.repository_name):
            continue
        # Find repo
        for r in config.get("cache_resource_templates.repositories", []):
            if r["name"] == change.principal.repository_name:
                repo_config = r
                repo = Repository(
                    r["repo_url"], r["name"], r["authentication_settings"]["email"]
                )
                await repo.clone(depth=1)
                git_client = repo.git
                git_client.reset()
                git_client.checkout(b=generated_branch_name)
                repositories_for_request[change.principal.repository_name] = {
                    "main_branch_name": r["main_branch_name"],
                    "repo": repo,
                    "git_client": git_client,
                    "config": r,
                }
                discovered_repository_for_change = True
                break
        if not discovered_repository_for_change:
            raise Exception(
                "No matching repository found for change in ConsoleMe's configuration"
            )
    request_changes = ChangeModelArray(changes=[])
    affected_templates = []
    for change in request_creation.changes.changes:
        git_client = repositories_for_request[change.principal.repository_name][
            "git_client"
        ]
        repo = repositories_for_request[change.principal.repository_name]["repo"].repo
        main_branch_name = repositories_for_request[change.principal.repository_name][
            "main_branch_name"
        ]
        git_client.checkout(
            f"origin/{main_branch_name}", change.principal.resource_identifier
        )
        change_file_path = f"{repo.working_dir}/{change.principal.resource_identifier}"
        with open(change_file_path, "r") as f:
            yaml_content = yaml.load(f)

        # Original
        buf = io.BytesIO()
        yaml.dump(yaml_content, buf)
        original_text = buf.getvalue()
        successfully_merged_statement = False
        if not yaml_content.get("Policies"):
            yaml_content["Policies"] = []
        if isinstance(yaml_content["Policies"], dict):
            yaml_content["Policies"] = [yaml_content["Policies"]]

        # The PolicyModel is a representation of a single (usually inline) policy that a user has requested be merged
        # into a given template. If the policy is provided as a string, it's the contents of the full file (which
        # should include the user's requested change)
        if isinstance(change.policy, PolicyModel):
            if isinstance(change.policy.policy_document["Statement"], str):
                change.policy.policy_document["Statement"] = [
                    change.policy.policy_document["Statement"]
                ]
            for i in range(len(yaml_content.get("Policies", []))):
                policy = yaml_content["Policies"][i]
                if policy.get("PolicyName") != policy_name:
                    continue
                if policy.get("IncludeAccounts") or policy.get("ExcludeAccounts"):
                    raise ValueError(
                        f"The {policy_name} policy has IncludeAccounts or ExcludeAccounts set"
                    )
                successfully_merged_statement = True

                policy["Statement"].extend(
                    CommentedSeq(change.policy.policy_document["Statement"])
                )
                yaml_content["Policies"][i][
                    "Statement"
                ] = await minimize_iam_policy_statements(
                    json.loads(json.dumps(policy["Statement"]))
                )
            if not successfully_merged_statement:
                yaml_content["Policies"].append(
                    {
                        "PolicyName": policy_name,
                        "Statement": change.policy.policy_document["Statement"],
                    }
                )
            with open(change_file_path, "w") as f:
                yaml.dump(yaml_content, f)
            # New
            buf = io.BytesIO()
            yaml.dump(yaml_content, buf)
            updated_text = buf.getvalue()

        elif isinstance(change.policy, str):
            # If the change is provided as a string, it represents the full change
            updated_text = change.policy
            with open(change_file_path, "w") as f:
                f.write(updated_text)
        else:
            raise Exception(
                "Unable to parse change from Honeybee templated role change request"
            )

        request_changes.changes.append(
            GenericFileChangeModel(
                principal=primary_principal,
                action="attach",
                change_type="generic_file",
                policy=updated_text,
                old_policy=original_text,
                encoding="yaml",
            )
        )
        git_client.add(change.principal.resource_identifier)
        affected_templates.append(change.principal.resource_identifier)

    pull_request_url = ""
    if not request_creation.dry_run:
        commit_title = f"ConsoleMe Generated PR for {', '.join(affected_templates)}"
        commit_message = (
            f"This request was made through ConsoleMe Self Service\n\nUser: {user}\n\n"
            f"Justification: {request_creation.justification}"
        )

        git_client.commit(m=commit_message)
        git_client.push(u=["origin", generated_branch_name])
        if repo_config["code_repository_provider"] == "bitbucket":
            bitbucket = BitBucket(
                repo_config["code_repository_config"]["url"],
                config.get(
                    repo_config["code_repository_config"]["username_config_key"]
                ),
                config.get(
                    repo_config["code_repository_config"]["password_config_key"]
                ),
            )
            pull_request_url = await bitbucket.create_pull_request(
                repo_config["project_key"],
                repo_config["name"],
                repo_config["project_key"],
                repo_config["name"],
                generated_branch_name,
                repo_config["main_branch_name"],
                commit_title,
                commit_message,
            )
        else:
            raise Exception(
                f"Unsupported `code_repository_provider` specified in configuration: {repo_config}"
            )

    for repo_name, repo_details in repositories_for_request.items():
        await repo_details["repo"].cleanup()

    if not pull_request_url and not request_creation.dry_run:
        raise Exception("Unable to generate pull request URL")

    return ExtendedRequestModel(
        id=extended_request_uuid,
        request_url=pull_request_url,
        principal=primary_principal,
        timestamp=int(time.time()),
        requester_email=user,
        approvers=[],
        request_status=RequestStatus.pending,
        changes=request_changes,
        requester_info=UserModel(
            email=user,
            extended_info=await auth.get_user_info(user),

async def generate_honeybee_request_from_change_model_array(
    request_creation: RequestCreationModel, user: str, extended_request_uuid: str
) -> ExtendedRequestModel:
    repositories_for_request = {}
    primary_principal = None
    t = int(time.time())
    suffix = "".join(
        random.choices(string.ascii_lowercase + string.digits, k=10)  # nosec
    )
    generated_branch_name = f"{user}-{t}-{suffix}"
    policy_name = config.get(
        "generate_honeybee_request_from_change_model_array.policy_name",
        "self_service_generated",
    )
    repo_config = None

    # Checkout Git Repo and generate a branch name for the user's change
    for change in request_creation.changes.changes:
        if primary_principal and change.principal != primary_principal:
            raise Exception("Changes must all affect the same principal")
        primary_principal = change.principal
        discovered_repository_for_change = False
        if repositories_for_request.get(change.principal.repository_name):
            continue
        # Find repo
        for r in config.get("cache_resource_templates.repositories", []):
            if r["name"] == change.principal.repository_name:
                repo_config = r
                repo = Repository(
                    r["repo_url"], r["name"], r["authentication_settings"]["email"]
                )
                await repo.clone(depth=1)
                git_client = repo.git
                git_client.reset()
                git_client.checkout(b=generated_branch_name)
                repositories_for_request[change.principal.repository_name] = {
                    "main_branch_name": r["main_branch_name"],
                    "repo": repo,
                    "git_client": git_client,
                    "config": r,
                }
                discovered_repository_for_change = True
                break
        if not discovered_repository_for_change:
            raise Exception(
                "No matching repository found for change in ConsoleMe's configuration"
            )
    request_changes = ChangeModelArray(changes=[])
    affected_templates = []
    for change in request_creation.changes.changes:
        git_client = repositories_for_request[change.principal.repository_name][
            "git_client"
        ]
        repo = repositories_for_request[change.principal.repository_name]["repo"].repo
        main_branch_name = repositories_for_request[change.principal.repository_name][
            "main_branch_name"
        ]

        change_file_path = os.path.abspath(
            f"{repo.working_dir}/{change.principal.resource_identifier}"
        )
        clone_wd_path = os.path.abspath(repo.working_dir)
        if os.path.commonprefix((clone_wd_path, change_file_path)) != clone_wd_path:
            log.exception(
                f"User attempted to reference a file outside of the repository: {change_file_path} is not within {clone_wd_path}"
            )
            raise ValueError("Unable to raise change request for this resource")

        try:
            git_client.checkout(
                f"origin/{main_branch_name}", "--", change.principal.resource_identifier
            )
        except Exception:
            log.exception(
                f"Unable to checkout {main_branch_name} for {change.principal.resource_identifier}"
            )
            raise ValueError("Unable to raise change request for this resource")
        with open(change_file_path, "r") as f:
            yaml_content = yaml.load(f)

        # Original
        buf = io.BytesIO()
        yaml.dump(yaml_content, buf)
        original_text = buf.getvalue()
        successfully_merged_statement = False
        if not yaml_content.get("Policies"):
            yaml_content["Policies"] = []
        if isinstance(yaml_content["Policies"], dict):
            yaml_content["Policies"] = [yaml_content["Policies"]]

        # The PolicyModel is a representation of a single (usually inline) policy that a user has requested be merged
        # into a given template. If the policy is provided as a string, it's the contents of the full file (which
        # should include the user's requested change)
        if isinstance(change.policy, PolicyModel):
            if isinstance(change.policy.policy_document["Statement"], str):
                change.policy.policy_document["Statement"] = [
                    change.policy.policy_document["Statement"]
                ]
            for i in range(len(yaml_content.get("Policies", []))):
                policy = yaml_content["Policies"][i]
                if policy.get("PolicyName") != policy_name:
                    continue
                if policy.get("IncludeAccounts") or policy.get("ExcludeAccounts"):
                    raise ValueError(
                        f"The {policy_name} policy has IncludeAccounts or ExcludeAccounts set"
                    )
                successfully_merged_statement = True

                policy["Statement"].extend(
                    CommentedSeq(change.policy.policy_document["Statement"])
                )
                yaml_content["Policies"][i][
                    "Statement"
                ] = await minimize_iam_policy_statements(
                    json.loads(json.dumps(policy["Statement"]))
                )
            if not successfully_merged_statement:
                yaml_content["Policies"].append(
                    {
                        "PolicyName": policy_name,
                        "Statement": change.policy.policy_document["Statement"],
                    }
                )
            with open(change_file_path, "w") as f:
                yaml.dump(yaml_content, f)
            # New
            buf = io.BytesIO()
            yaml.dump(yaml_content, buf)
            updated_text = buf.getvalue()

        elif isinstance(change.policy, str):
            # If the change is provided as a string, it represents the full change
            updated_text = change.policy
            with open(change_file_path, "w") as f:
                f.write(updated_text)
        else:
            raise Exception(
                "Unable to parse change from Honeybee templated role change request"
            )

        request_changes.changes.append(
            GenericFileChangeModel(
                principal=primary_principal,
                action="attach",
                change_type="generic_file",
                policy=updated_text,
                old_policy=original_text,
                encoding="yaml",
            )
        )
        git_client.add(change.principal.resource_identifier)
        affected_templates.append(change.principal.resource_identifier)

    pull_request_url = ""
    if not request_creation.dry_run:
        commit_title = f"ConsoleMe Generated PR for {', '.join(affected_templates)}"
        commit_message = (
            f"This request was made through ConsoleMe Self Service\n\nUser: {user}\n\n"
            f"Justification: {request_creation.justification}"
        )

        git_client.commit(m=commit_message)
        git_client.push(u=["origin", generated_branch_name])
        if repo_config["code_repository_provider"] == "bitbucket":
            bitbucket = BitBucket(
                repo_config["code_repository_config"]["url"],
                config.get(
                    repo_config["code_repository_config"]["username_config_key"]
                ),
                config.get(
                    repo_config["code_repository_config"]["password_config_key"]
                ),
            )
            pull_request_url = await bitbucket.create_pull_request(
                repo_config["project_key"],
                repo_config["name"],
                repo_config["project_key"],
                repo_config["name"],
                generated_branch_name,
                repo_config["main_branch_name"],
                commit_title,
                commit_message,
            )
        else:
            raise Exception(
                f"Unsupported `code_repository_provider` specified in configuration: {repo_config}"
            )

    for repo_name, repo_details in repositories_for_request.items():
        await repo_details["repo"].cleanup()

    if not pull_request_url and not request_creation.dry_run:
        raise Exception("Unable to generate pull request URL")

    return ExtendedRequestModel(
        id=extended_request_uuid,
        request_url=pull_request_url,
        principal=primary_principal,
        timestamp=int(time.time()),
        requester_email=user,
        approvers=[],
        request_status=RequestStatus.pending,
        changes=request_changes,
        requester_info=UserModel(
            email=user,
            extended_info=await auth.get_user_info(user),

--- a/consoleme/lib/templated_resources/requests.py
+++ b/consoleme/lib/templated_resources/requests.py
@@ -4,7 +4,10 @@
     repositories_for_request = {}
     primary_principal = None
     t = int(time.time())
-    generated_branch_name = f"{user}-{t}"
+    suffix = "".join(
+        random.choices(string.ascii_lowercase + string.digits, k=10)  # nosec
+    )
+    generated_branch_name = f"{user}-{t}-{suffix}"
     policy_name = config.get(
         "generate_honeybee_request_from_change_model_array.policy_name",
         "self_service_generated",
@@ -52,10 +55,26 @@
         main_branch_name = repositories_for_request[change.principal.repository_name][
             "main_branch_name"
         ]
-        git_client.checkout(
-            f"origin/{main_branch_name}", change.principal.resource_identifier
+
+        change_file_path = os.path.abspath(
+            f"{repo.working_dir}/{change.principal.resource_identifier}"
         )
-        change_file_path = f"{repo.working_dir}/{change.principal.resource_identifier}"
+        clone_wd_path = os.path.abspath(repo.working_dir)
+        if os.path.commonprefix((clone_wd_path, change_file_path)) != clone_wd_path:
+            log.exception(
+                f"User attempted to reference a file outside of the repository: {change_file_path} is not within {clone_wd_path}"
+            )
+            raise ValueError("Unable to raise change request for this resource")
+
+        try:
+            git_client.checkout(
+                f"origin/{main_branch_name}", "--", change.principal.resource_identifier
+            )
+        except Exception:
+            log.exception(
+                f"Unable to checkout {main_branch_name} for {change.principal.resource_identifier}"
+            )
+            raise ValueError("Unable to raise change request for this resource")
         with open(change_file_path, "r") as f:
             yaml_content = yaml.load(f)


The package bestzip before 2.1.7 are vulnerable to Command Injection via the options param.

const nativeZip = options =>
  new Promise((resolve, reject) => {
    const sources = Array.isArray(options.source)
      ? options.source.join(" ")
      : options.source;
    const command = `zip --quiet --recurse-paths ${
      options.destination
    } ${sources}`;
    const zipProcess = cp.exec(command, {
      stdio: "inherit",
      cwd: options.cwd
    });
    zipProcess.on("error", reject);
    zipProcess.on("close", exitCode => {
      if (exitCode === 0) {
        resolve();
      } else {
        // exit code 12 means "nothing to do" right?
        //console.log('rejecting', zipProcess)
        reject(
          new Error(
            `Unexpected exit code from native zip command: ${exitCode}\n executed command '${command}'\n executed inin directory '${options.cwd ||
              process.cwd()}'`
          )
        );
      }
    });
  });

const nativeZip = options =>
  new Promise((resolve, reject) => {
    const cwd = options.cwd || process.cwd();
    const command = "zip";
    expandSources(cwd, options.source, (err, sources) => {
      const args = ["--quiet", "--recurse-paths", options.destination].concat(
        sources
      );
      const zipProcess = cp.spawn(command, args, {
        stdio: "inherit",
        cwd
      });
      zipProcess.on("error", reject);
      zipProcess.on("close", exitCode => {
        if (exitCode === 0) {
          resolve();
        } else {
          // exit code 12 means "nothing to do" right?
          //console.log('rejecting', zipProcess)
          reject(
            new Error(
              `Unexpected exit code from native zip: ${exitCode}\n executed command '${command} ${args.join(
                " "
              )}'\n executed in directory '${cwd}'`
            )
          );
        }
      });
    });
  });

--- a/lib/bestzip.js
+++ b/lib/bestzip.js
@@ -1,28 +1,30 @@
 const nativeZip = options =>
   new Promise((resolve, reject) => {
-    const sources = Array.isArray(options.source)
-      ? options.source.join(" ")
-      : options.source;
-    const command = `zip --quiet --recurse-paths ${
-      options.destination
-    } ${sources}`;
-    const zipProcess = cp.exec(command, {
-      stdio: "inherit",
-      cwd: options.cwd
-    });
-    zipProcess.on("error", reject);
-    zipProcess.on("close", exitCode => {
-      if (exitCode === 0) {
-        resolve();
-      } else {
-        // exit code 12 means "nothing to do" right?
-        //console.log('rejecting', zipProcess)
-        reject(
-          new Error(
-            `Unexpected exit code from native zip command: ${exitCode}\n executed command '${command}'\n executed inin directory '${options.cwd ||
-              process.cwd()}'`
-          )
-        );
-      }
+    const cwd = options.cwd || process.cwd();
+    const command = "zip";
+    expandSources(cwd, options.source, (err, sources) => {
+      const args = ["--quiet", "--recurse-paths", options.destination].concat(
+        sources
+      );
+      const zipProcess = cp.spawn(command, args, {
+        stdio: "inherit",
+        cwd
+      });
+      zipProcess.on("error", reject);
+      zipProcess.on("close", exitCode => {
+        if (exitCode === 0) {
+          resolve();
+        } else {
+          // exit code 12 means "nothing to do" right?
+          //console.log('rejecting', zipProcess)
+          reject(
+            new Error(
+              `Unexpected exit code from native zip: ${exitCode}\n executed command '${command} ${args.join(
+                " "
+              )}'\n executed in directory '${cwd}'`
+            )
+          );
+        }
+      });
     });
   });


snapd 2.54.2 and earlier created ~/snap directories in user home directories without specifying owner-only permissions. This could allow a local attacker to read information that should have been private. Fixed in snapd versions 2.54.3+18.04, 2.54.3+20.04 and 2.54.3+21.10.1

Incorrect Default Permissions

During installation, installed file permissions are set to allow anyone to modify those files.

func createUserDataDirs(info *snap.Info) error {
	// Adjust umask so that the created directories have the permissions we
	// expect and are unaffected by the initial umask. While go runtime creates
	// threads at will behind the scenes, the setting of umask applies to the
	// entire process so it doesn't need any special handling to lock the
	// executing goroutine to a single thread.
	oldUmask := syscall.Umask(0)
	defer syscall.Umask(oldUmask)

	usr, err := userCurrent()
	if err != nil {
		return fmt.Errorf(i18n.G("cannot get the current user: %v"), err)
	}

	// see snapenv.User
	instanceUserData := info.UserDataDir(usr.HomeDir)
	instanceCommonUserData := info.UserCommonDataDir(usr.HomeDir)
	createDirs := []string{instanceUserData, instanceCommonUserData}
	if info.InstanceKey != "" {
		// parallel instance snaps get additional mapping in their mount
		// namespace, namely /home/joe/snap/foo_bar ->
		// /home/joe/snap/foo, make sure that the mount point exists and
		// is owned by the user
		snapUserDir := snap.UserSnapDir(usr.HomeDir, info.SnapName())
		createDirs = append(createDirs, snapUserDir)
	}
	for _, d := range createDirs {
		if err := os.MkdirAll(d, 0755); err != nil {
			// TRANSLATORS: %q is the directory whose creation failed, %v the error message
			return fmt.Errorf(i18n.G("cannot create %q: %v"), d, err)
		}
	}

	if err := createOrUpdateUserDataSymlink(info, usr); err != nil {
		return err
	}

	return maybeRestoreSecurityContext(usr)
}

func createUserDataDirs(info *snap.Info) error {
	// Adjust umask so that the created directories have the permissions we
	// expect and are unaffected by the initial umask. While go runtime creates
	// threads at will behind the scenes, the setting of umask applies to the
	// entire process so it doesn't need any special handling to lock the
	// executing goroutine to a single thread.
	oldUmask := syscall.Umask(0)
	defer syscall.Umask(oldUmask)

	usr, err := userCurrent()
	if err != nil {
		return fmt.Errorf(i18n.G("cannot get the current user: %v"), err)
	}

	snapDir := filepath.Join(usr.HomeDir, dirs.UserHomeSnapDir)
	if err := os.MkdirAll(snapDir, 0700); err != nil {
		return fmt.Errorf(i18n.G("cannot create snap home dir: %w"), err)
	}
	// see snapenv.User
	instanceUserData := info.UserDataDir(usr.HomeDir)
	instanceCommonUserData := info.UserCommonDataDir(usr.HomeDir)
	createDirs := []string{instanceUserData, instanceCommonUserData}
	if info.InstanceKey != "" {
		// parallel instance snaps get additional mapping in their mount
		// namespace, namely /home/joe/snap/foo_bar ->
		// /home/joe/snap/foo, make sure that the mount point exists and
		// is owned by the user
		snapUserDir := snap.UserSnapDir(usr.HomeDir, info.SnapName())
		createDirs = append(createDirs, snapUserDir)
	}
	for _, d := range createDirs {
		if err := os.MkdirAll(d, 0755); err != nil {
			// TRANSLATORS: %q is the directory whose creation failed, %v the error message
			return fmt.Errorf(i18n.G("cannot create %q: %v"), d, err)
		}
	}

	if err := createOrUpdateUserDataSymlink(info, usr); err != nil {
		return err
	}

	return maybeRestoreSecurityContext(usr)
}

--- a/cmd/snap/cmd_run.go
+++ b/cmd/snap/cmd_run.go
@@ -12,6 +12,10 @@
 		return fmt.Errorf(i18n.G("cannot get the current user: %v"), err)
 	}

+	snapDir := filepath.Join(usr.HomeDir, dirs.UserHomeSnapDir)
+	if err := os.MkdirAll(snapDir, 0700); err != nil {
+		return fmt.Errorf(i18n.G("cannot create snap home dir: %w"), err)
+	}
 	// see snapenv.User
 	instanceUserData := info.UserDataDir(usr.HomeDir)
 	instanceCommonUserData := info.UserCommonDataDir(usr.HomeDir)

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

cd /workspace/snapd
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -v -timeout 30s github.com/snapcore/snapd/cmd/snap -check.f RunSuite.TestCreateSnapDirPermissions


pixl-class prior to 1.0.3 allows execution of arbitrary commands. The members argument of the create function can be controlled by users without any sanitization.

exports.create = function create(members) {
	// create new class using php-style syntax (sort of)
	if (!members) members = {};

	// setup constructor
	var constructor = null;

	// inherit from parent class
	if (members.__parent) {
		if (members.__construct) {
			// explicit constructor passed in
			constructor = members.__construct;
		}
		else {
			// inherit parent's constructor
			var code = members.__parent.toString();
			var args = code.substring( code.indexOf("(")+1, code.indexOf(")") );
			var inner_code = code.substring( code.indexOf("{")+1, code.lastIndexOf("}") );
			eval('constructor = function ('+args+') {'+inner_code+'};');
		}

		// inherit rest of parent members
		util.inherits(constructor, members.__parent);
		delete members.__parent;
	}
	else {
		// create new base class
		constructor = members.__construct || function() {};
	}
	delete members.__construct;

	// handle static variables
	if (members.__static) {
		for (var key in members.__static) {
			constructor[key] = members.__static[key];
		}
		delete members.__static;
	}

	// all classes are event emitters unless explicitly disabled
	if (members.__events !== false) {
		if (!members.__mixins) members.__mixins = [];
		if (members.__mixins.indexOf(events.EventEmitter) == -1) {
			members.__mixins.push( events.EventEmitter );
		}
	}
	delete members.__events;

	// handle mixins
	if (members.__mixins) {
		for (var idx = 0, len = members.__mixins.length; idx < len; idx++) {
			var class_obj = members.__mixins[idx];

			for (var key in class_obj.prototype) {
				if (!key.match(/^__/) && (typeof(constructor.prototype[key]) == 'undefined')) {
					constructor.prototype[key] = class_obj.prototype[key];
				}
			}
			var static_members = class_obj.__static;
			if (static_members) {
				for (var key in static_members) {
					if (typeof(constructor[key]) == 'undefined') constructor[key] = static_members[key];
				}
			}
		} // foreach mixin
		delete members.__mixins;
	} // mixins

	// handle promisify (node 8+)
	if (members.__promisify && util.promisify) {
		if (Array.isArray(members.__promisify)) {
			// promisify some
			members.__promisify.forEach( function(key) {
				if (typeof(members[key]) == 'function') {
					members[key] = util.promisify( members[key] );
				}
			} );
		}
		else {
			// promisify all
			for (var key in members) {
				if (!key.match(/^__/) && (typeof(members[key]) == 'function')) {
					members[key] = util.promisify( members[key] );
				}
			}
		}
		delete members.__promisify;
	}

	// fill prototype members
	for (var key in members) {
		constructor.prototype[key] = members[key];
	}

	// return completed class definition
	return constructor;
};

exports.create = function create(members) {
	// create new class using php-style syntax (sort of)
	if (!members) members = {};

	// setup constructor
	var constructor = null;

	// inherit from parent class
	if (members.__parent) {
		if (members.__construct) {
			// explicit constructor passed in
			constructor = members.__construct;
		}
		else {
			// inherit parent's constructor
			var parent = members.__parent;
			constructor = function() {
				var args = Array.prototype.slice.call(arguments);
				parent.apply( this, args );
			};
		}

		// inherit rest of parent members
		util.inherits(constructor, members.__parent);
		delete members.__parent;
	}
	else {
		// create new base class
		constructor = members.__construct || function() {};
	}
	delete members.__construct;

	// handle static variables
	if (members.__static) {
		for (var key in members.__static) {
			constructor[key] = members.__static[key];
		}
		delete members.__static;
	}

	// all classes are event emitters unless explicitly disabled
	if (members.__events !== false) {
		if (!members.__mixins) members.__mixins = [];
		if (members.__mixins.indexOf(events.EventEmitter) == -1) {
			members.__mixins.push( events.EventEmitter );
		}
	}
	delete members.__events;

	// handle mixins
	if (members.__mixins) {
		for (var idx = 0, len = members.__mixins.length; idx < len; idx++) {
			var class_obj = members.__mixins[idx];

			for (var key in class_obj.prototype) {
				if (!key.match(/^__/) && (typeof(constructor.prototype[key]) == 'undefined')) {
					constructor.prototype[key] = class_obj.prototype[key];
				}
			}
			var static_members = class_obj.__static;
			if (static_members) {
				for (var key in static_members) {
					if (typeof(constructor[key]) == 'undefined') constructor[key] = static_members[key];
				}
			}
		} // foreach mixin
		delete members.__mixins;
	} // mixins

	// handle promisify (node 8+)
	if (members.__promisify && util.promisify) {
		if (Array.isArray(members.__promisify)) {
			// promisify some
			members.__promisify.forEach( function(key) {
				if (typeof(members[key]) == 'function') {
					members[key] = util.promisify( members[key] );
				}
			} );
		}
		else {
			// promisify all
			for (var key in members) {
				if (!key.match(/^__/) && (typeof(members[key]) == 'function')) {
					members[key] = util.promisify( members[key] );
				}
			}
		}
		delete members.__promisify;
	}

	// fill prototype members
	for (var key in members) {
		constructor.prototype[key] = members[key];
	}

	// return completed class definition
	return constructor;
};

--- a/class.js
+++ b/class.js
@@ -13,10 +13,11 @@
 		}
 		else {
 			// inherit parent's constructor
-			var code = members.__parent.toString();
-			var args = code.substring( code.indexOf("(")+1, code.indexOf(")") );
-			var inner_code = code.substring( code.indexOf("{")+1, code.lastIndexOf("}") );
-			eval('constructor = function ('+args+') {'+inner_code+'};');
+			var parent = members.__parent;
+			constructor = function() {
+				var args = Array.prototype.slice.call(arguments);
+				parent.apply( this, args );
+			};
 		}

 		// inherit rest of parent members

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

cd /workspace/pixl-class
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
jest ./poc


Due to improper parameter filtering in the Feathers js library, which may ultimately lead to SQL injection

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

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

  _find (params = {}) {
    const { filters, query: where, paginate } = this.filterQuery(params);
    const order = utils.getOrder(filters.$sort);

    const q = Object.assign({
      where,
      order,
      limit: filters.$limit,
      offset: filters.$skip,
      raw: this.raw,
      distinct: true
    }, params.sequelize);

    if (filters.$select) {
      q.attributes = filters.$select;
    }

    const Model = this.applyScope(params);

    // Until Sequelize fix all the findAndCount issues, a few 'hacks' are needed to get the total count correct

    // Adding an empty include changes the way the count is done
    // See: https://github.com/sequelize/sequelize/blob/7e441a6a5ca44749acd3567b59b1d6ceb06ae64b/lib/model.js#L1780-L1782
    q.include = q.include || [];

    if (paginate && paginate.default) {
      return Model.findAndCountAll(q).then(result => {
        return {
          total: result.count,
          limit: filters.$limit,
          skip: filters.$skip || 0,
          data: result.rows
        };
      }).catch(utils.errorHandler);
    }

    return Model.findAll(q).catch(utils.errorHandler);
  }

  _find (params = {}) {
    const { filters, query: where, paginate } = this.filterQuery(params);
    const order = utils.getOrder(filters.$sort);

    const q = Object.assign({
      where,
      order,
      limit: filters.$limit,
      offset: filters.$skip,
      raw: this.raw,
      distinct: true
    }, params.sequelize);

    if (filters.$select) {
      q.attributes = filters.$select.map(select => `${select}`);
    }

    const Model = this.applyScope(params);

    // Until Sequelize fix all the findAndCount issues, a few 'hacks' are needed to get the total count correct

    // Adding an empty include changes the way the count is done
    // See: https://github.com/sequelize/sequelize/blob/7e441a6a5ca44749acd3567b59b1d6ceb06ae64b/lib/model.js#L1780-L1782
    q.include = q.include || [];

    if (paginate && paginate.default) {
      return Model.findAndCountAll(q).then(result => {
        return {
          total: result.count,
          limit: filters.$limit,
          skip: filters.$skip || 0,
          data: result.rows
        };
      }).catch(utils.errorHandler);
    }

    return Model.findAll(q).catch(utils.errorHandler);
  }

--- a/lib/index.js
+++ b/lib/index.js
@@ -12,7 +12,7 @@
     }, params.sequelize);

     if (filters.$select) {
-      q.attributes = filters.$select;
+      q.attributes = filters.$select.map(select => `${select}`);
     }

     const Model = this.applyScope(params);

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

cd /workspace/feathers-sequelize
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --timeout 5000 --grep "does not allow raw attribute $select"

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

cd /workspace/feathers-sequelize
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha --timeout 5000 test/index.test.js


sagemaker-python-sdk is a library for training and deploying machine learning models on Amazon SageMaker. In affected versions the capture_dependencies function in `sagemaker.serve.save_retrive.version_1_0_0.save.utils` module allows for potentially unsafe Operating System (OS) Command Injection if inappropriate command is passed as the “requirements_path” parameter. This consequently may allow an unprivileged third party to cause remote code execution, denial of service, affecting both confidentiality and integrity. This issue has been addressed in version 2.214.3. Users are advised to upgrade. Users unable to upgrade should not override the “requirements_path” parameter of capture_dependencies function in `sagemaker.serve.save_retrive.version_1_0_0.save.utils`, and instead use the default value.

def capture_dependencies(requirements_path: str):
    """Placeholder docstring"""
    logger.info("Capturing dependencies...")

    try:
        import pigar

        pigar.__version__  # pylint: disable=W0104
    except ModuleNotFoundError:
        logger.warning(
            "pigar module is not installed in python environment, "
            "dependency generation may be incomplete"
            "Checkout the instructions on the installation page of its repo: "
            "https://github.com/damnever/pigar "
            "And follow the ones that match your environment."
            "Please note that you may need to restart your runtime after installation."
        )
        import sagemaker

        sagemaker_dependency = f"{sagemaker.__package__}=={sagemaker.__version__}"
        with open(requirements_path, "w") as f:
            f.write(sagemaker_dependency)
        return

    command = f"pigar gen -f {Path(requirements_path)} {os.getcwd()}"
    logging.info("Running command %s", command)

    os.system(command)
    logger.info("Dependencies captured successfully")

def capture_dependencies(requirements_path: str):
    """Placeholder docstring"""
    logger.info("Capturing dependencies...")

    try:
        import pigar

        pigar.__version__  # pylint: disable=W0104
    except ModuleNotFoundError:
        logger.warning(
            "pigar module is not installed in python environment, "
            "dependency generation may be incomplete"
            "Checkout the instructions on the installation page of its repo: "
            "https://github.com/damnever/pigar "
            "And follow the ones that match your environment."
            "Please note that you may need to restart your runtime after installation."
        )
        import sagemaker

        sagemaker_dependency = f"{sagemaker.__package__}=={sagemaker.__version__}"
        with open(requirements_path, "w") as f:
            f.write(sagemaker_dependency)
        return

    command = ["pigar", "gen", "-f", str(Path(requirements_path)), str(os.getcwd())]
    logging.info("Running command: %s", " ".join(command))

    subprocess.run(command, check=True, capture_output=True)
    logger.info("Dependencies captured successfully")

--- a/src/sagemaker/serve/save_retrive/version_1_0_0/save/utils.py
+++ b/src/sagemaker/serve/save_retrive/version_1_0_0/save/utils.py
@@ -22,8 +22,8 @@
             f.write(sagemaker_dependency)
         return

-    command = f"pigar gen -f {Path(requirements_path)} {os.getcwd()}"
-    logging.info("Running command %s", command)
+    command = ["pigar", "gen", "-f", str(Path(requirements_path)), str(os.getcwd())]
+    logging.info("Running command: %s", " ".join(command))

-    os.system(command)
+    subprocess.run(command, check=True, capture_output=True)
     logger.info("Dependencies captured successfully")


pytorch-lightning is vulnerable to Deserialization of Untrusted Data

Deserialization of Untrusted Data

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

def load_hparams_from_yaml(config_yaml: str, use_omegaconf: bool = True) -> Dict[str, Any]:
    """Load hparams from a file.

        Args:
            config_yaml: Path to config yaml file
            use_omegaconf: If omegaconf is available and ``use_omegaconf=True``,
                the hparams will be converted to ``DictConfig`` if possible.

    >>> hparams = Namespace(batch_size=32, learning_rate=0.001, data_root='./any/path/here')
    >>> path_yaml = './testing-hparams.yaml'
    >>> save_hparams_to_yaml(path_yaml, hparams)
    >>> hparams_new = load_hparams_from_yaml(path_yaml)
    >>> vars(hparams) == hparams_new
    True
    >>> os.remove(path_yaml)
    """
    fs = get_filesystem(config_yaml)
    if not fs.exists(config_yaml):
        rank_zero_warn(f"Missing Tags: {config_yaml}.", category=RuntimeWarning)
        return {}

    with fs.open(config_yaml, "r") as fp:
        hparams = yaml.load(fp, Loader=yaml.UnsafeLoader)

    if _OMEGACONF_AVAILABLE:
        if use_omegaconf:
            try:
                return OmegaConf.create(hparams)
            except (UnsupportedValueType, ValidationError):
                pass
    return hparams

def load_hparams_from_yaml(config_yaml: str, use_omegaconf: bool = True) -> Dict[str, Any]:
    """Load hparams from a file.

        Args:
            config_yaml: Path to config yaml file
            use_omegaconf: If omegaconf is available and ``use_omegaconf=True``,
                the hparams will be converted to ``DictConfig`` if possible.

    >>> hparams = Namespace(batch_size=32, learning_rate=0.001, data_root='./any/path/here')
    >>> path_yaml = './testing-hparams.yaml'
    >>> save_hparams_to_yaml(path_yaml, hparams)
    >>> hparams_new = load_hparams_from_yaml(path_yaml)
    >>> vars(hparams) == hparams_new
    True
    >>> os.remove(path_yaml)
    """
    fs = get_filesystem(config_yaml)
    if not fs.exists(config_yaml):
        rank_zero_warn(f"Missing Tags: {config_yaml}.", category=RuntimeWarning)
        return {}

    with fs.open(config_yaml, "r") as fp:
        hparams = yaml.full_load(fp)

    if _OMEGACONF_AVAILABLE:
        if use_omegaconf:
            try:
                return OmegaConf.create(hparams)
            except (UnsupportedValueType, ValidationError):
                pass
    return hparams

--- a/pytorch_lightning/core/saving.py
+++ b/pytorch_lightning/core/saving.py
@@ -20,7 +20,7 @@
         return {}

     with fs.open(config_yaml, "r") as fp:
-        hparams = yaml.load(fp, Loader=yaml.UnsafeLoader)
+        hparams = yaml.full_load(fp)

     if _OMEGACONF_AVAILABLE:
         if use_omegaconf: