A vulnerability was found in cyanomiko dcnnt-py up to 0.9.0. It has been classified as critical. Affected is the function main of the file dcnnt/plugins/notifications.py of the component Notification Handler. The manipulation leads to command injection. It is possible to launch the attack remotely. Upgrading to version 0.9.1 is able to address this issue. The patch is identified as b4021d784a97e25151a5353aa763a741e9a148f5. It is recommended to upgrade the affected component. VDB-262230 is the identifier assigned to this vulnerability.

    def main(self):
        while True:
            request = self.rpc_read()
            self.log(request)
            if request is None:
                self.log('No more requests, stop handler')
                return
            cmd = self.conf('cmd')
            if not cmd:
                return
            if request.method == 'notification':
                icon_data = self.read() if request.params.get('packageIcon', False) else None
                if request.params.get('event') == 'posted':
                    text, package = map(request.params.get, ('text', 'package'))
                    title = request.params.get('title', 'NULL')
                    name, uin = self.device.name, self.device.uin
                    if text is None:
                        text = ''
                    icon_path = os.path.join(self.conf('icon_dir'), f'{package}.{self.device.uin}.icon.png')
                    if bool(icon_data):
                        try:
                            open(icon_path, 'wb').write(icon_data)
                        except Exception as e:
                            self.log(e, logging.WARNING)
                    icon = icon_path if icon_data else ''
                    command = cmd.format(uin=uin, name=name, icon=icon, text=text, title=title, package=package)
                    self.log('Execute: "{}"'.format(command))
                    subprocess.call(command, shell=True)

    def main(self):
        while True:
            request = self.rpc_read()
            self.log(request)
            if request is None:
                self.log('No more requests, stop handler')
                return
            cmd = self.conf('cmd')
            if not cmd:
                return
            if request.method == 'notification':
                icon_data = self.read() if request.params.get('packageIcon', False) else None
                if request.params.get('event') == 'posted':
                    text, package = map(request.params.get, ('text', 'package'))
                    title = request.params.get('title', 'NULL')
                    name, uin = self.device.name, self.device.uin
                    if text is None:
                        text = ''
                    icon_path = os.path.join(self.conf('icon_dir'), f'{package}.{self.device.uin}.icon.png')
                    if bool(icon_data):
                        try:
                            open(icon_path, 'wb').write(icon_data)
                        except Exception as e:
                            self.log(e, logging.WARNING)
                    icon = icon_path if icon_data else ''
                    command = cmd.format(uin=quote(uin), name=quote(name), icon=quote(icon), text=quote(text), title=quote(title), package=quote(package))
                    self.log('Execute: "{}"'.format(command))
                    subprocess.call(command, shell=True)

--- a/dcnnt/plugins/notifications.py
+++ b/dcnnt/plugins/notifications.py
@@ -23,6 +23,6 @@
                         except Exception as e:
                             self.log(e, logging.WARNING)
                     icon = icon_path if icon_data else ''
-                    command = cmd.format(uin=uin, name=name, icon=icon, text=text, title=title, package=package)
+                    command = cmd.format(uin=quote(uin), name=quote(name), icon=quote(icon), text=quote(text), title=quote(title), package=quote(package))
                     self.log('Execute: "{}"'.format(command))
                     subprocess.call(command, shell=True)


ftp-srv is an open-source FTP server designed to be simple yet configurable. In ftp-srv before version 4.4.0 there is a path-traversal vulnerability. Clients of FTP servers utilizing ftp-srv hosted on Windows machines can escape the FTP user's defined root folder using the expected FTP commands, for example, CWD and UPDR. When windows separators exist within the path (`\`), `path.resolve` leaves the upper pointers intact and allows the user to move beyond the root folder defined for that user. We did not take that into account when creating the path resolve function. The issue is patched in version 4.4.0 (commit 457b859450a37cba10ff3c431eb4aa67771122e3).

External Control of File Name or Path

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

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

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

  constructor(connection, {root, cwd} = {}) {
    this.connection = connection;
    this.cwd = nodePath.normalize(cwd ? nodePath.join(nodePath.sep, cwd) : nodePath.sep);
    this._root = nodePath.resolve(root || process.cwd());
  }

  _resolvePath(path = '.') {
    const clientPath = (() => {
      path = nodePath.normalize(path);
      if (nodePath.isAbsolute(path)) {
        return nodePath.join(path);
      } else {
        return nodePath.join(this.cwd, path);
      }
    })();

    const fsPath = (() => {
      const resolvedPath = nodePath.join(this.root, clientPath);
      return nodePath.resolve(nodePath.normalize(nodePath.join(resolvedPath)));
    })();

    return {
      clientPath,
      fsPath
    };
  }

  constructor(connection, {root, cwd} = {}) {
    this.connection = connection;
    this.cwd = nodePath.normalize((cwd || '/').replace(WIN_SEP_REGEX, '/'));
    this._root = nodePath.resolve(root || process.cwd());
  }

  _resolvePath(path = '.') {
    // Unix separators normalize nicer on both unix and win platforms
    const resolvedPath = path.replace(WIN_SEP_REGEX, '/');

    // Join cwd with new path
    const joinedPath = nodePath.isAbsolute(resolvedPath)
      ? nodePath.normalize(resolvedPath)
      : nodePath.join('/', this.cwd, resolvedPath);

    // Create local filesystem path using the platform separator
    const fsPath = nodePath.resolve(nodePath.join(this.root, joinedPath)
      .replace(UNIX_SEP_REGEX, nodePath.sep)
      .replace(WIN_SEP_REGEX, nodePath.sep));

    // Create FTP client path using unix separator
    const clientPath = joinedPath.replace(WIN_SEP_REGEX, '/');

    return {
      clientPath,
      fsPath
    };
  }

const UNIX_SEP_REGEX = /\//g;
const WIN_SEP_REGEX = /\\/g;

--- a/src/fs.js
+++ b/src/fs.js
@@ -1,5 +1,5 @@
   constructor(connection, {root, cwd} = {}) {
     this.connection = connection;
-    this.cwd = nodePath.normalize(cwd ? nodePath.join(nodePath.sep, cwd) : nodePath.sep);
+    this.cwd = nodePath.normalize((cwd || '/').replace(WIN_SEP_REGEX, '/'));
     this._root = nodePath.resolve(root || process.cwd());
   }

--- a/src/fs.js
+++ b/src/fs.js
@@ -1,17 +1,19 @@
   _resolvePath(path = '.') {
-    const clientPath = (() => {
-      path = nodePath.normalize(path);
-      if (nodePath.isAbsolute(path)) {
-        return nodePath.join(path);
-      } else {
-        return nodePath.join(this.cwd, path);
-      }
-    })();
+    // Unix separators normalize nicer on both unix and win platforms
+    const resolvedPath = path.replace(WIN_SEP_REGEX, '/');

-    const fsPath = (() => {
-      const resolvedPath = nodePath.join(this.root, clientPath);
-      return nodePath.resolve(nodePath.normalize(nodePath.join(resolvedPath)));
-    })();
+    // Join cwd with new path
+    const joinedPath = nodePath.isAbsolute(resolvedPath)
+      ? nodePath.normalize(resolvedPath)
+      : nodePath.join('/', this.cwd, resolvedPath);
+
+    // Create local filesystem path using the platform separator
+    const fsPath = nodePath.resolve(nodePath.join(this.root, joinedPath)
+      .replace(UNIX_SEP_REGEX, nodePath.sep)
+      .replace(WIN_SEP_REGEX, nodePath.sep));
+
+    // Create FTP client path using unix separator
+    const clientPath = joinedPath.replace(WIN_SEP_REGEX, '/');

     return {
       clientPath,

--- /dev/null
+++ b/src/fs.js
@@ -0,0 +1,2 @@
+const UNIX_SEP_REGEX = /\//g;
+const WIN_SEP_REGEX = /\\/g;

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

cd /workspace/ftp-srv
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --grep "cannot escape root - win" --exit


utils/find-opencv.js in node-opencv (aka OpenCV bindings for Node.js) prior to 6.1.0 is vulnerable to Command Injection. It does not validate user input allowing attackers to execute arbitrary commands.

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.

--- a/utils/find-opencv.js
+++ b/utils/find-opencv.js
@@ -1 +1 @@
-var flag = process.argv[2] || "--exists";
+var flag = "--exists";


capsule-proxy is a reverse proxy for the capsule operator project. Affected versions are subject to a privilege escalation vulnerability which is based on a missing check if the user is authenticated based on the `TokenReview` result. All the clusters running with the `anonymous-auth` Kubernetes API Server setting disable (set to `false`) are affected since it would be possible to bypass the token review mechanism, interacting with the upper Kubernetes API Server. This privilege escalation cannot be exploited if you're relying only on client certificates (SSL/TLS). This vulnerability has been addressed in version 0.4.6. Users are advised to upgrade.

Improper Authentication

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

func (h http) processBearerToken() (username string, groups []string, err error) {
	tr := &authenticationv1.TokenReview{
		Spec: authenticationv1.TokenReviewSpec{
			Token: h.bearerToken(),
		},
	}

	if err = h.client.Create(h.Request.Context(), tr); err != nil {
		return "", nil, fmt.Errorf("cannot create TokenReview")
	}

	if statusErr := tr.Status.Error; len(statusErr) > 0 {
		return "", nil, fmt.Errorf("cannot verify the token due to error")
	}

	return tr.Status.User.Username, tr.Status.User.Groups, nil
}

func (h http) processBearerToken() (username string, groups []string, err error) {
	tr := &authenticationv1.TokenReview{
		Spec: authenticationv1.TokenReviewSpec{
			Token: h.bearerToken(),
		},
	}

	if err = h.client.Create(h.Request.Context(), tr); err != nil {
		return "", nil, fmt.Errorf("cannot create TokenReview")
	}

	if !tr.Status.Authenticated {
		return "", nil, fmt.Errorf("cannot verify the token due to error")
	}

	if statusErr := tr.Status.Error; len(statusErr) > 0 {
		return "", nil, fmt.Errorf("cannot verify the token due to error")
	}

	return tr.Status.User.Username, tr.Status.User.Groups, nil
}

--- a/internal/request/http.go
+++ b/internal/request/http.go
@@ -9,6 +9,10 @@
 		return "", nil, fmt.Errorf("cannot create TokenReview")
 	}

+	if !tr.Status.Authenticated {
+		return "", nil, fmt.Errorf("cannot verify the token due to error")
+	}
+
 	if statusErr := tr.Status.Error; len(statusErr) > 0 {
 		return "", nil, fmt.Errorf("cannot verify the token due to error")
 	}


In ginadmin through 05-10-2022 the incoming path value is not filtered, resulting in directory traversal.

func (con adminSystemController) GetDir(c *gin.Context) {

	type FileNode struct {
		Name string `json:"name"`
		Path string `json:"path"`
		Type string `json:"type"`
	}

	var (
		path      string
		err       error
		fileSlice []FileNode
		files     []fs.FileInfo
	)

	fileSlice = make([]FileNode, 0)
	path = gstrings.JoinStr(configs.RootPath, c.Query("path"))

	files, err = ioutil.ReadDir(path)
	if err != nil {
		con.Error(c, "\u83b7\u53d6\u76ee\u5f55\u5931\u8d25")
		return
	}

	for _, v := range files {
		var fileType string
		if v.IsDir() {
			fileType = "dir"
		} else {
			fileType = "file"
		}
		fileSlice = append(fileSlice, FileNode{
			Name: v.Name(),
			Path: gstrings.JoinStr(c.Query("path"), string(filepath.Separator), v.Name()),
			Type: fileType,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"data": fileSlice,
	})
}

func (con adminSystemController) View(c *gin.Context) {

	var (
		err       error
		startLine int
		endLine   int
		scanner   *bufio.Scanner
		line      int
	)

	startLine, err = strconv.Atoi(c.DefaultQuery("start_line", "1"))
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}
	endLine, err = strconv.Atoi(c.DefaultQuery("end_line", "20"))
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}

	var filecontents []string
	filePath := gstrings.JoinStr(configs.RootPath, c.Query("path"))
	fi, err := os.Open(filePath)
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}
	defer fi.Close()

	scanner = bufio.NewScanner(fi)
	for scanner.Scan() {
		line++
		if line >= startLine && line <= endLine {
			// \u5728\u8981\u6c42\u884c\u6570\u5185\u53d6\u5f97\u6570\u636e
			filecontents = append(filecontents, scanner.Text())
		} else {
			continue
		}
	}

	c.HTML(http.StatusOK, "setting/systemlog_view.html", gin.H{
		"file_path":    c.Query("path"),
		"filecontents": filecontents,
		"start_line":   startLine,
		"end_line":     endLine,
		"line":         line,
	})

}

func JoinStr(items ...interface{}) string {
	if len(items) == 0 {
		return ""
	}
	var builder strings.Builder
	for _, v := range items {
		builder.WriteString(v.(string))
	}
	return builder.String()
}

func (con adminSystemController) GetDir(c *gin.Context) {

	type FileNode struct {
		Name string `json:"name"`
		Path string `json:"path"`
		Type string `json:"type"`
	}

	var (
		path      string
		err       error
		fileSlice []FileNode
		files     []fs.FileInfo
	)

	fileSlice = make([]FileNode, 0)
	path, err = filesystem.FilterPath(configs.RootPath+"logs", c.Query("path"))
	if err != nil {
		con.Error(c, err.Error())
		return
	}

	files, err = ioutil.ReadDir(path)
	if err != nil {
		con.Error(c, "\u83b7\u53d6\u76ee\u5f55\u5931\u8d25")
		return
	}

	for _, v := range files {
		var fileType string
		if v.IsDir() {
			fileType = "dir"
		} else {
			fileType = "file"
		}
		fileSlice = append(fileSlice, FileNode{
			Name: v.Name(),
			Path: gstrings.JoinStr(c.Query("path"), string(filepath.Separator), v.Name()),
			Type: fileType,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"data": fileSlice,
	})
}

func (con adminSystemController) View(c *gin.Context) {

	var (
		err       error
		startLine int
		endLine   int
		scanner   *bufio.Scanner
		line      int
	)

	startLine, err = strconv.Atoi(c.DefaultQuery("start_line", "1"))
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}
	endLine, err = strconv.Atoi(c.DefaultQuery("end_line", "20"))
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}

	var filecontents []string
	filePath, err := filesystem.FilterPath(configs.RootPath+"logs", c.Query("path"))
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}

	fi, err := os.Open(filePath)
	if err != nil {
		con.ErrorHtml(c, err)
		return
	}
	defer fi.Close()

	scanner = bufio.NewScanner(fi)
	for scanner.Scan() {
		line++
		if line >= startLine && line <= endLine {
			// \u5728\u8981\u6c42\u884c\u6570\u5185\u53d6\u5f97\u6570\u636e
			filecontents = append(filecontents, scanner.Text())
		} else {
			continue
		}
	}

	c.HTML(http.StatusOK, "setting/systemlog_view.html", gin.H{
		"file_path":    c.Query("path"),
		"filecontents": filecontents,
		"start_line":   startLine,
		"end_line":     endLine,
		"line":         line,
	})

}

func FilterPath(root, path string) (string, error) {

	newPath := fmt.Sprintf("%s%s", root, path)
	absPath, err := filepath.Abs(newPath)
	if err != nil {
		return "", err
	}

	absPath = filepath.FromSlash(absPath)
	ifOver := filepath.HasPrefix(absPath, filepath.FromSlash(root))
	fmt.Println(absPath)
	fmt.Println(filepath.FromSlash(root))
	if !ifOver {
		return "", errors.New("access to the path is prohibited")
	}

	return absPath, nil
}

--- a/internal/controllers/admin/setting/adminSystemController.go
+++ b/internal/controllers/admin/setting/adminSystemController.go
@@ -14,7 +14,11 @@
 	)

 	fileSlice = make([]FileNode, 0)
-	path = gstrings.JoinStr(configs.RootPath, c.Query("path"))
+	path, err = filesystem.FilterPath(configs.RootPath+"logs", c.Query("path"))
+	if err != nil {
+		con.Error(c, err.Error())
+		return
+	}

 	files, err = ioutil.ReadDir(path)
 	if err != nil {

--- a/internal/controllers/admin/setting/adminSystemController.go
+++ b/internal/controllers/admin/setting/adminSystemController.go
@@ -20,7 +20,12 @@
 	}

 	var filecontents []string
-	filePath := gstrings.JoinStr(configs.RootPath, c.Query("path"))
+	filePath, err := filesystem.FilterPath(configs.RootPath+"logs", c.Query("path"))
+	if err != nil {
+		con.ErrorHtml(c, err)
+		return
+	}
+
 	fi, err := os.Open(filePath)
 	if err != nil {
 		con.ErrorHtml(c, err)

--- a/pkg/utils/filesystem/filesystem.go
+++ b/pkg/utils/filesystem/filesystem.go
@@ -1,10 +1,18 @@
-func JoinStr(items ...interface{}) string {
-	if len(items) == 0 {
-		return ""
+func FilterPath(root, path string) (string, error) {
+
+	newPath := fmt.Sprintf("%s%s", root, path)
+	absPath, err := filepath.Abs(newPath)
+	if err != nil {
+		return "", err
 	}
-	var builder strings.Builder
-	for _, v := range items {
-		builder.WriteString(v.(string))
+
+	absPath = filepath.FromSlash(absPath)
+	ifOver := filepath.HasPrefix(absPath, filepath.FromSlash(root))
+	fmt.Println(absPath)
+	fmt.Println(filepath.FromSlash(root))
+	if !ifOver {
+		return "", errors.New("access to the path is prohibited")
 	}
-	return builder.String()
+
+	return absPath, nil
 }


Express OpenID Connect is an Express JS middleware implementing sign on for Express web apps using OpenID Connect. Users of the `requiresAuth` middleware, either directly or through the default `authRequired` option, are vulnerable to an Open Redirect when the middleware is applied to a catch all route. If all routes under `example.com` are protected with the `requiresAuth` middleware, a visit to `http://example.com//google.com` will be redirected to `google.com` after login because the original url reported by the Express framework is not properly sanitized. This vulnerability affects versions prior to 2.7.2. Users are advised to upgrade. There are no known workarounds.

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.

  async login(options = {}) {
    let { config, req, res, next, transient } = weakRef(this);
    next = cb(next).once();
    const client = await getClient(config);

    // Set default returnTo value, allow passed-in options to override or use originalUrl on GET
    let returnTo = config.baseURL;
    if (options.returnTo) {
      returnTo = options.returnTo;
      debug('req.oidc.login() called with returnTo: %s', returnTo);
    } else if (req.method === 'GET' && req.originalUrl) {
      returnTo = req.originalUrl;
      debug('req.oidc.login() without returnTo, using: %s', returnTo);
    }

    options = {
      authorizationParams: {},
      returnTo,
      ...options,
    };

    // Ensure a redirect_uri, merge in configuration options, then passed-in options.
    options.authorizationParams = {
      redirect_uri: this.getRedirectUri(),
      ...config.authorizationParams,
      ...options.authorizationParams,
    };

    const stateValue = await config.getLoginState(req, options);
    if (typeof stateValue !== 'object') {
      next(new Error('Custom state value must be an object.'));
    }
    stateValue.nonce = transient.generateNonce();
    if (options.silent) {
      stateValue.attemptingSilentLogin = true;
    }

    const usePKCE = options.authorizationParams.response_type.includes('code');
    if (usePKCE) {
      debug(
        'response_type includes code, the authorization request will use PKCE'
      );
      stateValue.code_verifier = transient.generateCodeVerifier();
    }

    try {
      const validResponseTypes = ['id_token', 'code id_token', 'code'];
      assert(
        validResponseTypes.includes(options.authorizationParams.response_type),
        `response_type should be one of ${validResponseTypes.join(', ')}`
      );
      assert(
        /\bopenid\b/.test(options.authorizationParams.scope),
        'scope should contain "openid"'
      );

      const authVerification = {
        nonce: transient.generateNonce(),
        state: encodeState(stateValue),
        ...(options.authorizationParams.max_age
          ? {
              max_age: options.authorizationParams.max_age,
            }
          : undefined),
      };
      const authParams = {
        ...options.authorizationParams,
        ...authVerification,
      };

      if (usePKCE) {
        authVerification.code_verifier = transient.generateNonce();

        authParams.code_challenge_method = 'S256';
        authParams.code_challenge = transient.calculateCodeChallenge(
          authVerification.code_verifier
        );
      }

      transient.store('auth_verification', req, res, {
        sameSite:
          options.authorizationParams.response_mode === 'form_post'
            ? 'None'
            : config.transactionCookie.sameSite,
        value: JSON.stringify(authVerification),
      });

      const authorizationUrl = client.authorizationUrl(authParams);
      debug('redirecting to %s', authorizationUrl);
      res.redirect(authorizationUrl);
    } catch (err) {
      next(err);
    }
  }

  async login(options = {}) {
    let { config, req, res, next, transient } = weakRef(this);
    next = cb(next).once();
    const client = await getClient(config);

    // Set default returnTo value, allow passed-in options to override or use originalUrl on GET
    let returnTo = config.baseURL;
    if (options.returnTo) {
      returnTo = options.returnTo;
      debug('req.oidc.login() called with returnTo: %s', returnTo);
    } else if (req.method === 'GET' && req.originalUrl) {
      // Collapse any leading slashes to a single slash to prevent Open Redirects
      returnTo = req.originalUrl.replace(/^\/+/, '/');
      debug('req.oidc.login() without returnTo, using: %s', returnTo);
    }

    options = {
      authorizationParams: {},
      returnTo,
      ...options,
    };

    // Ensure a redirect_uri, merge in configuration options, then passed-in options.
    options.authorizationParams = {
      redirect_uri: this.getRedirectUri(),
      ...config.authorizationParams,
      ...options.authorizationParams,
    };

    const stateValue = await config.getLoginState(req, options);
    if (typeof stateValue !== 'object') {
      next(new Error('Custom state value must be an object.'));
    }
    stateValue.nonce = transient.generateNonce();
    if (options.silent) {
      stateValue.attemptingSilentLogin = true;
    }

    const usePKCE = options.authorizationParams.response_type.includes('code');
    if (usePKCE) {
      debug(
        'response_type includes code, the authorization request will use PKCE'
      );
      stateValue.code_verifier = transient.generateCodeVerifier();
    }

    try {
      const validResponseTypes = ['id_token', 'code id_token', 'code'];
      assert(
        validResponseTypes.includes(options.authorizationParams.response_type),
        `response_type should be one of ${validResponseTypes.join(', ')}`
      );
      assert(
        /\bopenid\b/.test(options.authorizationParams.scope),
        'scope should contain "openid"'
      );

      const authVerification = {
        nonce: transient.generateNonce(),
        state: encodeState(stateValue),
        ...(options.authorizationParams.max_age
          ? {
              max_age: options.authorizationParams.max_age,
            }
          : undefined),
      };
      const authParams = {
        ...options.authorizationParams,
        ...authVerification,
      };

      if (usePKCE) {
        authVerification.code_verifier = transient.generateNonce();

        authParams.code_challenge_method = 'S256';
        authParams.code_challenge = transient.calculateCodeChallenge(
          authVerification.code_verifier
        );
      }

      transient.store('auth_verification', req, res, {
        sameSite:
          options.authorizationParams.response_mode === 'form_post'
            ? 'None'
            : config.transactionCookie.sameSite,
        value: JSON.stringify(authVerification),
      });

      const authorizationUrl = client.authorizationUrl(authParams);
      debug('redirecting to %s', authorizationUrl);
      res.redirect(authorizationUrl);
    } catch (err) {
      next(err);
    }
  }

--- a/lib/context.js
+++ b/lib/context.js
@@ -9,7 +9,8 @@
       returnTo = options.returnTo;
       debug('req.oidc.login() called with returnTo: %s', returnTo);
     } else if (req.method === 'GET' && req.originalUrl) {
-      returnTo = req.originalUrl;
+      // Collapse any leading slashes to a single slash to prevent Open Redirects
+      returnTo = req.originalUrl.replace(/^\/+/, '/');
       debug('req.oidc.login() without returnTo, using: %s', returnTo);
     }

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

cd /workspace/express-openid-connect
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --grep "should collapse leading slashes on returnTo"

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

cd /workspace/express-openid-connect
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha test/requiresAuth.tests.js


OpenFGA is an authorization/permission engine. Prior to version 0.2.4, the `streamed-list-objects` endpoint was not validating the authorization header, resulting in disclosure of objects in the store. Users `openfga/openfga` versions 0.2.3 and prior who are exposing the OpenFGA service to the internet are vulnerable. Version 0.2.4 contains a patch for this issue.

Missing Authorization

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

Authorization Bypass Through User-Controlled Key

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

func BuildService(config *Config, logger logger.Logger) (*service, error) {
	tracer := telemetry.NewNoopTracer()
	meter := telemetry.NewNoopMeter()
	tokenEncoder := encoder.NewTokenEncoder(encrypter.NewNoopEncrypter(), encoder.NewBase64Encoder())

	var datastore storage.OpenFGADatastore
	var err error
	switch config.Datastore.Engine {
	case "memory":
		datastore = memory.New(tracer, config.MaxTuplesPerWrite, config.MaxTypesPerAuthorizationModel)
	case "mysql":
		opts := []mysql.MySQLOption{
			mysql.WithLogger(logger),
			mysql.WithTracer(tracer),
		}

		datastore, err = mysql.NewMySQLDatastore(config.Datastore.URI, opts...)
		if err != nil {
			return nil, errors.Errorf("failed to initialize mysql datastore: %v", err)
		}
	case "postgres":
		opts := []postgres.PostgresOption{
			postgres.WithLogger(logger),
			postgres.WithTracer(tracer),
		}

		datastore, err = postgres.NewPostgresDatastore(config.Datastore.URI, opts...)
		if err != nil {
			return nil, errors.Errorf("failed to initialize postgres datastore: %v", err)
		}
	default:
		return nil, errors.Errorf("storage engine '%s' is unsupported", config.Datastore.Engine)
	}

	logger.Info(fmt.Sprintf("using '%v' storage engine", config.Datastore.Engine))

	var grpcTLSConfig *server.TLSConfig
	if config.GRPC.TLS.Enabled {
		if config.GRPC.TLS.CertPath == "" || config.GRPC.TLS.KeyPath == "" {
			return nil, ErrInvalidGRPCTLSConfig
		}
		grpcTLSConfig = &server.TLSConfig{
			CertPath: config.GRPC.TLS.CertPath,
			KeyPath:  config.GRPC.TLS.KeyPath,
		}
		logger.Info("grpc TLS is enabled, serving connections using the provided certificate")
	} else {
		logger.Warn("grpc TLS is disabled, serving connections using insecure plaintext")
	}

	var httpTLSConfig *server.TLSConfig
	if config.HTTP.TLS.Enabled {
		if config.HTTP.TLS.CertPath == "" || config.HTTP.TLS.KeyPath == "" {
			return nil, ErrInvalidHTTPTLSConfig
		}
		httpTLSConfig = &server.TLSConfig{
			CertPath: config.HTTP.TLS.CertPath,
			KeyPath:  config.HTTP.TLS.KeyPath,
		}
		logger.Info("HTTP TLS is enabled, serving HTTP connections using the provided certificate")
	} else {
		logger.Warn("HTTP TLS is disabled, serving connections using insecure plaintext")
	}

	var authenticator authn.Authenticator
	switch config.Authn.Method {
	case "none":
		logger.Warn("authentication is disabled")
		authenticator = authn.NoopAuthenticator{}
	case "preshared":
		logger.Info("using 'preshared' authentication")
		authenticator, err = presharedkey.NewPresharedKeyAuthenticator(config.Authn.Keys)
	case "oidc":
		logger.Info("using 'oidc' authentication")
		authenticator, err = oidc.NewRemoteOidcAuthenticator(config.Authn.Issuer, config.Authn.Audience)
	default:
		return nil, errors.Errorf("unsupported authentication method '%v'", config.Authn.Method)
	}
	if err != nil {
		return nil, errors.Errorf("failed to initialize authenticator: %v", err)
	}

	interceptors := []grpc.UnaryServerInterceptor{
		grpc_auth.UnaryServerInterceptor(middleware.AuthFunc(authenticator)),
		middleware.NewErrorLoggingInterceptor(logger),
	}

	grpcHostAddr, grpcHostPort, err := net.SplitHostPort(config.GRPC.Addr)
	if err != nil {
		return nil, errors.Errorf("`grpc.addr` config must be in the form [host]:port")
	}

	if grpcHostAddr == "" {
		grpcHostAddr = "0.0.0.0"
	}

	grpcAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%s", grpcHostAddr, grpcHostPort))
	if err != nil {
		return nil, errors.Errorf("failed to parse the 'grpc.addr' config: %v", err)
	}

	httpHostAddr, httpHostPort, err := net.SplitHostPort(config.HTTP.Addr)
	if err != nil {
		return nil, errors.Errorf("`http.addr` config must be in the form [host]:port")
	}

	if httpHostAddr == "" {
		httpHostAddr = "0.0.0.0"
	}

	httpAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%s", httpHostAddr, httpHostPort))
	if err != nil {
		return nil, errors.Errorf("failed to parse the 'http.addr' config: %v", err)
	}

	openFgaServer, err := server.New(&server.Dependencies{
		Datastore:    caching.NewCachedOpenFGADatastore(datastore, config.Datastore.MaxCacheSize),
		Tracer:       tracer,
		Logger:       logger,
		Meter:        meter,
		TokenEncoder: tokenEncoder,
	}, &server.Config{
		GRPCServer: server.GRPCServerConfig{
			Addr:      grpcAddr,
			TLSConfig: grpcTLSConfig,
		},
		HTTPServer: server.HTTPServerConfig{
			Enabled:            config.HTTP.Enabled,
			Addr:               httpAddr,
			TLSConfig:          httpTLSConfig,
			UpstreamTimeout:    config.HTTP.UpstreamTimeout,
			CORSAllowedOrigins: config.HTTP.CORSAllowedOrigins,
			CORSAllowedHeaders: config.HTTP.CORSAllowedHeaders,
		},
		ResolveNodeLimit:       config.ResolveNodeLimit,
		ChangelogHorizonOffset: config.ChangelogHorizonOffset,
		ListObjectsDeadline:    config.ListObjectsDeadline,
		ListObjectsMaxResults:  config.ListObjectsMaxResults,
		UnaryInterceptors:      interceptors,
		MuxOptions:             nil,
	})
	if err != nil {
		return nil, errors.Errorf("failed to initialize openfga server: %v", err)
	}

	return &service{
		server:        openFgaServer,
		grpcAddr:      grpcAddr,
		httpAddr:      httpAddr,
		datastore:     datastore,
		authenticator: authenticator,
	}, nil
}

func BuildService(config *Config, logger logger.Logger) (*service, error) {
	tracer := telemetry.NewNoopTracer()
	meter := telemetry.NewNoopMeter()
	tokenEncoder := encoder.NewTokenEncoder(encrypter.NewNoopEncrypter(), encoder.NewBase64Encoder())

	var datastore storage.OpenFGADatastore
	var err error
	switch config.Datastore.Engine {
	case "memory":
		datastore = memory.New(tracer, config.MaxTuplesPerWrite, config.MaxTypesPerAuthorizationModel)
	case "mysql":
		opts := []mysql.MySQLOption{
			mysql.WithLogger(logger),
			mysql.WithTracer(tracer),
		}

		datastore, err = mysql.NewMySQLDatastore(config.Datastore.URI, opts...)
		if err != nil {
			return nil, errors.Errorf("failed to initialize mysql datastore: %v", err)
		}
	case "postgres":
		opts := []postgres.PostgresOption{
			postgres.WithLogger(logger),
			postgres.WithTracer(tracer),
		}

		datastore, err = postgres.NewPostgresDatastore(config.Datastore.URI, opts...)
		if err != nil {
			return nil, errors.Errorf("failed to initialize postgres datastore: %v", err)
		}
	default:
		return nil, errors.Errorf("storage engine '%s' is unsupported", config.Datastore.Engine)
	}

	logger.Info(fmt.Sprintf("using '%v' storage engine", config.Datastore.Engine))

	var grpcTLSConfig *server.TLSConfig
	if config.GRPC.TLS.Enabled {
		if config.GRPC.TLS.CertPath == "" || config.GRPC.TLS.KeyPath == "" {
			return nil, ErrInvalidGRPCTLSConfig
		}
		grpcTLSConfig = &server.TLSConfig{
			CertPath: config.GRPC.TLS.CertPath,
			KeyPath:  config.GRPC.TLS.KeyPath,
		}
		logger.Info("grpc TLS is enabled, serving connections using the provided certificate")
	} else {
		logger.Warn("grpc TLS is disabled, serving connections using insecure plaintext")
	}

	var httpTLSConfig *server.TLSConfig
	if config.HTTP.TLS.Enabled {
		if config.HTTP.TLS.CertPath == "" || config.HTTP.TLS.KeyPath == "" {
			return nil, ErrInvalidHTTPTLSConfig
		}
		httpTLSConfig = &server.TLSConfig{
			CertPath: config.HTTP.TLS.CertPath,
			KeyPath:  config.HTTP.TLS.KeyPath,
		}
		logger.Info("HTTP TLS is enabled, serving HTTP connections using the provided certificate")
	} else {
		logger.Warn("HTTP TLS is disabled, serving connections using insecure plaintext")
	}

	var authenticator authn.Authenticator
	switch config.Authn.Method {
	case "none":
		logger.Warn("authentication is disabled")
		authenticator = authn.NoopAuthenticator{}
	case "preshared":
		logger.Info("using 'preshared' authentication")
		authenticator, err = presharedkey.NewPresharedKeyAuthenticator(config.Authn.Keys)
	case "oidc":
		logger.Info("using 'oidc' authentication")
		authenticator, err = oidc.NewRemoteOidcAuthenticator(config.Authn.Issuer, config.Authn.Audience)
	default:
		return nil, errors.Errorf("unsupported authentication method '%v'", config.Authn.Method)
	}
	if err != nil {
		return nil, errors.Errorf("failed to initialize authenticator: %v", err)
	}

	unaryServerInterceptors := []grpc.UnaryServerInterceptor{
		grpc_auth.UnaryServerInterceptor(middleware.AuthFunc(authenticator)),
		middleware.NewErrorLoggingInterceptor(logger),
	}

	streamingServerInterceptors := []grpc.StreamServerInterceptor{
		grpc_auth.StreamServerInterceptor(middleware.AuthFunc(authenticator)),
		middleware.NewStreamingErrorLoggingInterceptor(logger),
	}

	grpcHostAddr, grpcHostPort, err := net.SplitHostPort(config.GRPC.Addr)
	if err != nil {
		return nil, errors.Errorf("`grpc.addr` config must be in the form [host]:port")
	}

	if grpcHostAddr == "" {
		grpcHostAddr = "0.0.0.0"
	}

	grpcAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%s", grpcHostAddr, grpcHostPort))
	if err != nil {
		return nil, errors.Errorf("failed to parse the 'grpc.addr' config: %v", err)
	}

	httpHostAddr, httpHostPort, err := net.SplitHostPort(config.HTTP.Addr)
	if err != nil {
		return nil, errors.Errorf("`http.addr` config must be in the form [host]:port")
	}

	if httpHostAddr == "" {
		httpHostAddr = "0.0.0.0"
	}

	httpAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%s", httpHostAddr, httpHostPort))
	if err != nil {
		return nil, errors.Errorf("failed to parse the 'http.addr' config: %v", err)
	}

	openFgaServer, err := server.New(&server.Dependencies{
		Datastore:    caching.NewCachedOpenFGADatastore(datastore, config.Datastore.MaxCacheSize),
		Tracer:       tracer,
		Logger:       logger,
		Meter:        meter,
		TokenEncoder: tokenEncoder,
	}, &server.Config{
		GRPCServer: server.GRPCServerConfig{
			Addr:      grpcAddr,
			TLSConfig: grpcTLSConfig,
		},
		HTTPServer: server.HTTPServerConfig{
			Enabled:            config.HTTP.Enabled,
			Addr:               httpAddr,
			TLSConfig:          httpTLSConfig,
			UpstreamTimeout:    config.HTTP.UpstreamTimeout,
			CORSAllowedOrigins: config.HTTP.CORSAllowedOrigins,
			CORSAllowedHeaders: config.HTTP.CORSAllowedHeaders,
		},
		ResolveNodeLimit:       config.ResolveNodeLimit,
		ChangelogHorizonOffset: config.ChangelogHorizonOffset,
		ListObjectsDeadline:    config.ListObjectsDeadline,
		ListObjectsMaxResults:  config.ListObjectsMaxResults,
		UnaryInterceptors:      unaryServerInterceptors,
		StreamingInterceptors:  streamingServerInterceptors,
		MuxOptions:             nil,
	})
	if err != nil {
		return nil, errors.Errorf("failed to initialize openfga server: %v", err)
	}

	return &service{
		server:        openFgaServer,
		grpcAddr:      grpcAddr,
		httpAddr:      httpAddr,
		datastore:     datastore,
		authenticator: authenticator,
	}, nil
}

--- a/pkg/cmd/service/service.go
+++ b/pkg/cmd/service/service.go
@@ -80,9 +80,14 @@
 		return nil, errors.Errorf("failed to initialize authenticator: %v", err)
 	}

-	interceptors := []grpc.UnaryServerInterceptor{
+	unaryServerInterceptors := []grpc.UnaryServerInterceptor{
 		grpc_auth.UnaryServerInterceptor(middleware.AuthFunc(authenticator)),
 		middleware.NewErrorLoggingInterceptor(logger),
+	}
+
+	streamingServerInterceptors := []grpc.StreamServerInterceptor{
+		grpc_auth.StreamServerInterceptor(middleware.AuthFunc(authenticator)),
+		middleware.NewStreamingErrorLoggingInterceptor(logger),
 	}

 	grpcHostAddr, grpcHostPort, err := net.SplitHostPort(config.GRPC.Addr)
@@ -136,7 +141,8 @@
 		ChangelogHorizonOffset: config.ChangelogHorizonOffset,
 		ListObjectsDeadline:    config.ListObjectsDeadline,
 		ListObjectsMaxResults:  config.ListObjectsMaxResults,
-		UnaryInterceptors:      interceptors,
+		UnaryInterceptors:      unaryServerInterceptors,
+		StreamingInterceptors:  streamingServerInterceptors,
 		MuxOptions:             nil,
 	})
 	if err != nil {

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

cd /workspace/openfga
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -timeout 30s -run ^TestBuildServiceWithPresharedKeyAuthentication$ github.com/openfga/openfga/pkg/cmd/service

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

cd /workspace/openfga
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestBuildServiceWithNoAuth|TestBuildServiceWithPresharedKeyAuthentication|TestGRPCServingTLS|TestBuildServerWithOIDCAuthentication|TestHTTPServerWithCORS|TestHTTPServingTLS|TestDefaultConfig|TestTLSFailureSettings|TestBuildServiceWithPresharedKeyAuthenticationFailsIfZeroKeys|TestHTTPServerEnabled|TestHTTPServerDisabled)$' github.com/openfga/openfga/pkg/cmd/service


In OAuth2 Proxy before 5.1.1, there is an open redirect vulnerability. Users can provide a redirect address for the proxy to send the authenticated user to at the end of the authentication flow. This is expected to be the original URL that the user was trying to access. This redirect URL is checked within the proxy and validated before redirecting the user to prevent malicious actors providing redirects to potentially harmful sites. However, by crafting a redirect URL with HTML encoded whitespace characters the validation could be bypassed and allow a redirect to any URL provided. This has been patched in 5.1.1.

func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
	switch {
	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !strings.HasPrefix(redirect, "/\\"):
		return true
	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
		redirectURL, err := url.Parse(redirect)
		if err != nil {
			logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect)
			return false
		}
		redirectHostname := redirectURL.Hostname()

		for _, domain := range p.whitelistDomains {
			domainHostname, domainPort := splitHostPort(strings.TrimLeft(domain, "."))
			if domainHostname == "" {
				continue
			}

			if (redirectHostname == domainHostname) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectHostname, domainHostname)) {
				// the domain names match, now validate the ports
				// if the whitelisted domain's port is '*', allow all ports
				// if the whitelisted domain contains a specific port, only allow that port
				// if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
				redirectPort := redirectURL.Port()
				if (domainPort == "*") ||
					(domainPort == redirectPort) ||
					(domainPort == "" && redirectPort == "") {
					return true
				}
			}
		}

		logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect)
		return false
	default:
		logger.Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect)
		return false
	}
}

var (
	// ErrNeedsLogin means the user should be redirected to the login page
	ErrNeedsLogin = errors.New("redirect to login page")

	// Used to check final redirects are not susceptible to open redirects.
	// Matches //, /\ and both of these with whitespace in between (eg / / or / \).
	invalidRedirectRegex = regexp.MustCompile(`^/(\s|\v)?(/|\\)`)
)

func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
	switch {
	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !invalidRedirectRegex.MatchString(redirect):
		return true
	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
		redirectURL, err := url.Parse(redirect)
		if err != nil {
			logger.Printf("Rejecting invalid redirect %q: scheme unsupported or missing", redirect)
			return false
		}
		redirectHostname := redirectURL.Hostname()

		for _, domain := range p.whitelistDomains {
			domainHostname, domainPort := splitHostPort(strings.TrimLeft(domain, "."))
			if domainHostname == "" {
				continue
			}

			if (redirectHostname == domainHostname) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectHostname, domainHostname)) {
				// the domain names match, now validate the ports
				// if the whitelisted domain's port is '*', allow all ports
				// if the whitelisted domain contains a specific port, only allow that port
				// if the whitelisted domain doesn't contain a port at all, only allow empty redirect ports ie http and https
				redirectPort := redirectURL.Port()
				if (domainPort == "*") ||
					(domainPort == redirectPort) ||
					(domainPort == "" && redirectPort == "") {
					return true
				}
			}
		}

		logger.Printf("Rejecting invalid redirect %q: domain / port not in whitelist", redirect)
		return false
	default:
		logger.Printf("Rejecting invalid redirect %q: not an absolute or relative URL", redirect)
		return false
	}
}

--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -1,6 +1,6 @@
 func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
 	switch {
-	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !strings.HasPrefix(redirect, "/\\"):
+	case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") && !invalidRedirectRegex.MatchString(redirect):
 		return true
 	case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
 		redirectURL, err := url.Parse(redirect)

--- /dev/null
+++ b/oauthproxy.go
@@ -0,0 +1,8 @@
+var (
+	// ErrNeedsLogin means the user should be redirected to the login page
+	ErrNeedsLogin = errors.New("redirect to login page")
+
+	// Used to check final redirects are not susceptible to open redirects.
+	// Matches //, /\ and both of these with whitespace in between (eg / / or / \).
+	invalidRedirectRegex = regexp.MustCompile(`^/(\s|\v)?(/|\\)`)
+)

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

cd /workspace/oauth2-proxy
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -timeout 30s -run ^TestIsValidRedirect$ github.com/oauth2-proxy/oauth2-proxy


setroubleshoot allows local users to bypass an intended container protection mechanism and execute arbitrary commands by (1) triggering an SELinux denial with a crafted file name, which is handled by the _set_tpath function in audit_data.py or via a crafted (2) local_id or (3) analysis_id field in a crafted XML document to the run_fix function in SetroubleshootFixit.py, related to the subprocess.check_output and commands.getstatusoutput functions, a different vulnerability than CVE-2016-4445.

    def run_fix(self, local_id, analysis_id):
        import commands
        command = "sealert -f %s -P %s" % ( local_id, analysis_id)
        return commands.getoutput(command)

    def run_fix(self, local_id, analysis_id):
        import subprocess
        command = ["sealert", "-f", local_id, "-P", analysis_id]
        return subprocess.check_output(command, universal_newlines=True)

--- a/framework/src/SetroubleshootFixit.py
+++ b/framework/src/SetroubleshootFixit.py
@@ -1,4 +1,4 @@
     def run_fix(self, local_id, analysis_id):
-        import commands
-        command = "sealert -f %s -P %s" % ( local_id, analysis_id)
-        return commands.getoutput(command)
+        import subprocess
+        command = ["sealert", "-f", local_id, "-P", analysis_id]
+        return subprocess.check_output(command, universal_newlines=True)


qutebrowser is an open source keyboard-focused browser with a minimal GUI. Starting with qutebrowser v1.7.0, the Windows installer for qutebrowser registers a `qutebrowserurl:` URL handler. With certain applications, opening a specially crafted `qutebrowserurl:...` URL can lead to execution of qutebrowser commands, which in turn allows arbitrary code execution via commands such as `:spawn` or `:debug-pyeval`. Only Windows installs where qutebrowser is registered as URL handler are affected. The issue has been fixed in qutebrowser v2.4.0. The fix also adds additional hardening for potential similar issues on Linux (by adding the new --untrusted-args flag to the .desktop file), though no such vulnerabilities are known.

def main():
    parser = get_argparser()
    argv = sys.argv[1:]
    args = parser.parse_args(argv)
    if args.json_args is not None:
        args = _unpack_json_args(args)
    earlyinit.early_init(args)
    # We do this imports late as earlyinit needs to be run first (because of
    # version checking and other early initialization)
    from qutebrowser import app
    return app.run(args)

def main():
    _validate_untrusted_args(sys.argv)
    parser = get_argparser()
    argv = sys.argv[1:]
    args = parser.parse_args(argv)
    if args.json_args is not None:
        args = _unpack_json_args(args)
    earlyinit.early_init(args)
    # We do this imports late as earlyinit needs to be run first (because of
    # version checking and other early initialization)
    from qutebrowser import app
    return app.run(args)

def _validate_untrusted_args(argv):
    # NOTE: Do not use f-strings here, as this should run with older Python
    # versions (so that a proper error can be displayed)
    try:
        untrusted_idx = argv.index('--untrusted-args')
    except ValueError:
        return

    rest = argv[untrusted_idx + 1:]
    if len(rest) > 1:
        sys.exit(
            "Found multiple arguments ({}) after --untrusted-args, "
            "aborting.".format(' '.join(rest)))

    for arg in rest:
        if arg.startswith(('-', ':')):
            sys.exit("Found {} after --untrusted-args, aborting.".format(arg))

--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -1,4 +1,5 @@
 def main():
+    _validate_untrusted_args(sys.argv)
     parser = get_argparser()
     argv = sys.argv[1:]
     args = parser.parse_args(argv)

--- /dev/null
+++ b/qutebrowser/qutebrowser.py
@@ -0,0 +1,18 @@
+def _validate_untrusted_args(argv):
+    # NOTE: Do not use f-strings here, as this should run with older Python
+    # versions (so that a proper error can be displayed)
+    try:
+        untrusted_idx = argv.index('--untrusted-args')
+    except ValueError:
+        return
+
+    rest = argv[untrusted_idx + 1:]
+    if len(rest) > 1:
+        sys.exit(
+            "Found multiple arguments ({}) after --untrusted-args, "
+            "aborting.".format(' '.join(rest)))
+
+    for arg in rest:
+        if arg.startswith(('-', ':')):
+            sys.exit("Found {} after --untrusted-args, aborting.".format(arg))
+


Cosign is a sigstore signing tool for OCI containers. Cosign is susceptible to a denial of service by an attacker controlled registry. An attacker who controls a remote registry can return a high number of attestations and/or signatures to Cosign and cause Cosign to enter a long loop resulting in an endless data attack. The root cause is that Cosign loops through all attestations fetched from the remote registry in pkg/cosign.FetchAttestations. The attacker needs to compromise the registry or make a request to a registry they control. When doing so, the attacker must return a high number of attestations in the response to Cosign. The result will be that the attacker can cause Cosign to go into a long or infinite loop that will prevent other users from verifying their data. In Kyvernos case, an attacker whose privileges are limited to making requests to the cluster can make a request with an image reference to their own registry, trigger the infinite loop and deny other users from completing their admission requests. Alternatively, the attacker can obtain control of the registry used by an organization and return a high number of attestations instead the expected number of attestations. The issue can be mitigated rather simply by setting a limit to the limit of attestations that Cosign will loop through. The limit does not need to be high to be within the vast majority of use cases and still prevent the endless data attack. This issue has been patched in version 2.2.1 and users are advised to upgrade.

Loop with Unreachable Exit Condition ('Infinite Loop')

The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.

func FetchSignaturesForReference(_ context.Context, ref name.Reference, opts ...ociremote.Option) ([]SignedPayload, error) {
	simg, err := ociremote.SignedEntity(ref, opts...)
	if err != nil {
		return nil, err
	}

	sigs, err := simg.Signatures()
	if err != nil {
		return nil, fmt.Errorf("remote image: %w", err)
	}
	l, err := sigs.Get()
	if err != nil {
		return nil, fmt.Errorf("fetching signatures: %w", err)
	}
	if len(l) == 0 {
		return nil, fmt.Errorf("no signatures associated with %s", ref)
	}

	signatures := make([]SignedPayload, len(l))
	var g errgroup.Group
	g.SetLimit(runtime.NumCPU())
	for i, sig := range l {
		i, sig := i, sig
		g.Go(func() error {
			var err error
			signatures[i].Payload, err = sig.Payload()
			if err != nil {
				return err
			}
			signatures[i].Base64Signature, err = sig.Base64Signature()
			if err != nil {
				return err
			}
			signatures[i].Cert, err = sig.Cert()
			if err != nil {
				return err
			}
			signatures[i].Chain, err = sig.Chain()
			if err != nil {
				return err
			}

			signatures[i].RFC3161Timestamp, err = sig.RFC3161Timestamp()
			if err != nil {
				return err
			}

			signatures[i].Bundle, err = sig.Bundle()
			return err
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}

	return signatures, nil
}

func FetchAttestations(se oci.SignedEntity, predicateType string) ([]AttestationPayload, error) {
	atts, err := se.Attestations()
	if err != nil {
		return nil, fmt.Errorf("remote image: %w", err)
	}
	l, err := atts.Get()
	if err != nil {
		return nil, fmt.Errorf("fetching attestations: %w", err)
	}
	if len(l) == 0 {
		return nil, errors.New("found no attestations")
	}

	attestations := make([]AttestationPayload, 0, len(l))
	var attMu sync.Mutex

	var g errgroup.Group
	g.SetLimit(runtime.NumCPU())

	for _, att := range l {
		att := att
		g.Go(func() error {
			rawPayload, err := att.Payload()
			if err != nil {
				return fmt.Errorf("fetching payload: %w", err)
			}
			var payload AttestationPayload
			if err := json.Unmarshal(rawPayload, &payload); err != nil {
				return fmt.Errorf("unmarshaling payload: %w", err)
			}

			if predicateType != "" {
				var decodedPayload []byte
				decodedPayload, err = base64.StdEncoding.DecodeString(payload.PayLoad)
				if err != nil {
					return fmt.Errorf("decoding payload: %w", err)
				}
				var statement in_toto.Statement
				if err := json.Unmarshal(decodedPayload, &statement); err != nil {
					return fmt.Errorf("unmarshaling statement: %w", err)
				}
				if statement.PredicateType != predicateType {
					return nil
				}
			}

			attMu.Lock()
			defer attMu.Unlock()
			attestations = append(attestations, payload)
			return nil
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}

	if len(attestations) == 0 && predicateType != "" {
		return nil, fmt.Errorf("no attestations with predicate type '%s' found", predicateType)
	}

	return attestations, nil
}

func FetchSignaturesForReference(_ context.Context, ref name.Reference, opts ...ociremote.Option) ([]SignedPayload, error) {
	simg, err := ociremote.SignedEntity(ref, opts...)
	if err != nil {
		return nil, err
	}

	sigs, err := simg.Signatures()
	if err != nil {
		return nil, fmt.Errorf("remote image: %w", err)
	}
	l, err := sigs.Get()
	if err != nil {
		return nil, fmt.Errorf("fetching signatures: %w", err)
	}
	if len(l) == 0 {
		return nil, fmt.Errorf("no signatures associated with %s", ref)
	}
	if len(l) > maxAllowedSigsOrAtts {
		return nil, fmt.Errorf("maximum number of signatures on an image is %d, found %d", maxAllowedSigsOrAtts, len(l))
	}

	signatures := make([]SignedPayload, len(l))
	var g errgroup.Group
	g.SetLimit(runtime.NumCPU())
	for i, sig := range l {
		i, sig := i, sig
		g.Go(func() error {
			var err error
			signatures[i].Payload, err = sig.Payload()
			if err != nil {
				return err
			}
			signatures[i].Base64Signature, err = sig.Base64Signature()
			if err != nil {
				return err
			}
			signatures[i].Cert, err = sig.Cert()
			if err != nil {
				return err
			}
			signatures[i].Chain, err = sig.Chain()
			if err != nil {
				return err
			}

			signatures[i].RFC3161Timestamp, err = sig.RFC3161Timestamp()
			if err != nil {
				return err
			}

			signatures[i].Bundle, err = sig.Bundle()
			return err
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}

	return signatures, nil
}

func FetchAttestations(se oci.SignedEntity, predicateType string) ([]AttestationPayload, error) {
	atts, err := se.Attestations()
	if err != nil {
		return nil, fmt.Errorf("remote image: %w", err)
	}
	l, err := atts.Get()
	if err != nil {
		return nil, fmt.Errorf("fetching attestations: %w", err)
	}
	if len(l) == 0 {
		return nil, errors.New("found no attestations")
	}
	if len(l) > maxAllowedSigsOrAtts {
		errMsg := fmt.Sprintf("maximum number of attestations on an image is %d, found %d", maxAllowedSigsOrAtts, len(l))
		return nil, errors.New(errMsg)
	}

	attestations := make([]AttestationPayload, 0, len(l))
	var attMu sync.Mutex

	var g errgroup.Group
	g.SetLimit(runtime.NumCPU())

	for _, att := range l {
		att := att
		g.Go(func() error {
			rawPayload, err := att.Payload()
			if err != nil {
				return fmt.Errorf("fetching payload: %w", err)
			}
			var payload AttestationPayload
			if err := json.Unmarshal(rawPayload, &payload); err != nil {
				return fmt.Errorf("unmarshaling payload: %w", err)
			}

			if predicateType != "" {
				var decodedPayload []byte
				decodedPayload, err = base64.StdEncoding.DecodeString(payload.PayLoad)
				if err != nil {
					return fmt.Errorf("decoding payload: %w", err)
				}
				var statement in_toto.Statement
				if err := json.Unmarshal(decodedPayload, &statement); err != nil {
					return fmt.Errorf("unmarshaling statement: %w", err)
				}
				if statement.PredicateType != predicateType {
					return nil
				}
			}

			attMu.Lock()
			defer attMu.Unlock()
			attestations = append(attestations, payload)
			return nil
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}

	if len(attestations) == 0 && predicateType != "" {
		return nil, fmt.Errorf("no attestations with predicate type '%s' found", predicateType)
	}

	return attestations, nil
}

--- a/pkg/cosign/fetch.go
+++ b/pkg/cosign/fetch.go
@@ -14,6 +14,9 @@
 	}
 	if len(l) == 0 {
 		return nil, fmt.Errorf("no signatures associated with %s", ref)
+	}
+	if len(l) > maxAllowedSigsOrAtts {
+		return nil, fmt.Errorf("maximum number of signatures on an image is %d, found %d", maxAllowedSigsOrAtts, len(l))
 	}

 	signatures := make([]SignedPayload, len(l))

--- a/pkg/cosign/fetch.go
+++ b/pkg/cosign/fetch.go
@@ -9,6 +9,10 @@
 	}
 	if len(l) == 0 {
 		return nil, errors.New("found no attestations")
+	}
+	if len(l) > maxAllowedSigsOrAtts {
+		errMsg := fmt.Sprintf("maximum number of attestations on an image is %d, found %d", maxAllowedSigsOrAtts, len(l))
+		return nil, errors.New(errMsg)
 	}

 	attestations := make([]AttestationPayload, 0, len(l))


An exploitable vulnerability exists in the YAML parsing functionality in the read_yaml_file method in io_utils.py in django_make_app 0.1.3. A YAML parser can execute arbitrary Python commands resulting in command execution. An attacker can insert Python into loaded YAML to trigger this vulnerability.

def read_yaml_file(filename):
    with io.open(filename, mode='r', encoding='utf-8') as the_file:
        return yaml.load(the_file)

def read_yaml_file(filename):
    with io.open(filename, mode='r', encoding='utf-8') as the_file:
        return yaml.safe_load(the_file)

--- a/django_make_app/io_utils.py
+++ b/django_make_app/io_utils.py
@@ -1,3 +1,3 @@
 def read_yaml_file(filename):
     with io.open(filename, mode='r', encoding='utf-8') as the_file:
-        return yaml.load(the_file)
+        return yaml.safe_load(the_file)


The imgcrypt library provides API exensions for containerd to support encrypted container images and implements the ctd-decoder command line tool for use by containerd to decrypt encrypted container images. The imgcrypt function `CheckAuthorization` is supposed to check whether the current used is authorized to access an encrypted image and prevent the user from running an image that another user previously decrypted on the same system. In versions prior to 1.1.4, a failure occurs when an image with a ManifestList is used and the architecture of the local host is not the first one in the ManifestList. Only the first architecture in the list was tested, which may not have its layers available locally since it could not be run on the host architecture. Therefore, the verdict on unavailable layers was that the image could be run anticipating that image run failure would occur later due to the layers not being available. However, this verdict to allow the image to run enabled other architectures in the ManifestList to run an image without providing keys if that image had previously been decrypted. A patch has been applied to imgcrypt 1.1.4. Workarounds may include usage of different namespaces for each remote user.

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.

// IsEncryptedDiff returns true if mediaType is a known encrypted media type.
func IsEncryptedDiff(ctx context.Context, mediaType string) bool {
	switch mediaType {
	case encocispec.MediaTypeLayerGzipEnc, encocispec.MediaTypeLayerEnc:
		return true
	}
	return false
}

// HasEncryptedLayer returns true if any LayerInfo indicates that the layer is encrypted
func HasEncryptedLayer(ctx context.Context, layerInfos []ocispec.Descriptor) bool {
	for i := 0; i < len(layerInfos); i++ {
		if IsEncryptedDiff(ctx, layerInfos[i].MediaType) {
			return true
		}
	}
	return false
}

// encryptLayer encrypts the layer using the CryptoConfig and creates a new OCI Descriptor.
// A call to this function may also only manipulate the wrapped keys list.
// The caller is expected to store the returned encrypted data and OCI Descriptor
func encryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor) (ocispec.Descriptor, io.Reader, ocicrypt.EncryptLayerFinalizer, error) {
	var (
		size int64
		d    digest.Digest
		err  error
	)

	encLayerReader, encLayerFinalizer, err := ocicrypt.EncryptLayer(cc.EncryptConfig, ocicrypt.ReaderFromReaderAt(dataReader), desc)
	if err != nil {
		return ocispec.Descriptor{}, nil, nil, err
	}

	// were data touched ?
	if encLayerReader != nil {
		size = 0
		d = ""
	} else {
		size = desc.Size
		d = desc.Digest
	}

	newDesc := ocispec.Descriptor{
		Digest:   d,
		Size:     size,
		Platform: desc.Platform,
	}

	switch desc.MediaType {
	case images.MediaTypeDockerSchema2LayerGzip:
		newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
	case images.MediaTypeDockerSchema2Layer:
		newDesc.MediaType = encocispec.MediaTypeLayerEnc
	case encocispec.MediaTypeLayerGzipEnc:
		newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
	case encocispec.MediaTypeLayerEnc:
		newDesc.MediaType = encocispec.MediaTypeLayerEnc

	// TODO: Mediatypes to be added in ocispec
	case ocispec.MediaTypeImageLayerGzip:
		newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
	case ocispec.MediaTypeImageLayer:
		newDesc.MediaType = encocispec.MediaTypeLayerEnc

	default:
		return ocispec.Descriptor{}, nil, nil, fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
	}

	return newDesc, encLayerReader, encLayerFinalizer, nil
}

// DecryptLayer decrypts the layer using the DecryptConfig and creates a new OCI Descriptor.
// The caller is expected to store the returned plain data and OCI Descriptor
func DecryptLayer(dc *encconfig.DecryptConfig, dataReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, digest.Digest, error) {
	resultReader, layerDigest, err := ocicrypt.DecryptLayer(dc, dataReader, desc, unwrapOnly)
	if err != nil || unwrapOnly {
		return ocispec.Descriptor{}, nil, "", err
	}

	newDesc := ocispec.Descriptor{
		Size:     0,
		Platform: desc.Platform,
	}

	switch desc.MediaType {
	case encocispec.MediaTypeLayerGzipEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzip
	case encocispec.MediaTypeLayerEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2Layer
	default:
		return ocispec.Descriptor{}, nil, "", fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
	}
	return newDesc, resultReader, layerDigest, nil
}

// decryptLayer decrypts the layer using the CryptoConfig and creates a new OCI Descriptor.
// The caller is expected to store the returned plain data and OCI Descriptor
func decryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, error) {
	resultReader, d, err := ocicrypt.DecryptLayer(cc.DecryptConfig, ocicrypt.ReaderFromReaderAt(dataReader), desc, unwrapOnly)
	if err != nil || unwrapOnly {
		return ocispec.Descriptor{}, nil, err
	}

	newDesc := ocispec.Descriptor{
		Digest:   d,
		Size:     0,
		Platform: desc.Platform,
	}

	switch desc.MediaType {
	case encocispec.MediaTypeLayerGzipEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzip
	case encocispec.MediaTypeLayerEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2Layer
	default:
		return ocispec.Descriptor{}, nil, fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
	}
	return newDesc, resultReader, nil
}

// cryptLayer handles the changes due to encryption or decryption of a layer
func cryptLayer(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, cryptoOp cryptoOp) (ocispec.Descriptor, error) {
	var (
		resultReader      io.Reader
		newDesc           ocispec.Descriptor
		encLayerFinalizer ocicrypt.EncryptLayerFinalizer
	)

	dataReader, err := cs.ReaderAt(ctx, desc)
	if err != nil {
		return ocispec.Descriptor{}, err
	}
	defer dataReader.Close()

	if cryptoOp == cryptoOpEncrypt {
		newDesc, resultReader, encLayerFinalizer, err = encryptLayer(cc, dataReader, desc)
	} else {
		newDesc, resultReader, err = decryptLayer(cc, dataReader, desc, cryptoOp == cryptoOpUnwrapOnly)
	}
	if err != nil || cryptoOp == cryptoOpUnwrapOnly {
		return ocispec.Descriptor{}, err
	}

	newDesc.Annotations = ocicrypt.FilterOutAnnotations(desc.Annotations)

	// some operations, such as changing recipients, may not touch the layer at all
	if resultReader != nil {
		var ref string
		// If we have the digest, write blob with checks
		haveDigest := newDesc.Digest.String() != ""
		if haveDigest {
			ref = fmt.Sprintf("layer-%s", newDesc.Digest.String())
		} else {
			ref = fmt.Sprintf("blob-%d-%d", rand.Int(), rand.Int())
		}

		if haveDigest {
			if err := content.WriteBlob(ctx, cs, ref, resultReader, newDesc); err != nil {
				return ocispec.Descriptor{}, fmt.Errorf("failed to write config: %w", err)
			}
		} else {
			newDesc.Digest, newDesc.Size, err = ingestReader(ctx, cs, ref, resultReader)
			if err != nil {
				return ocispec.Descriptor{}, err
			}
		}
	}

	// After performing encryption, call finalizer to get annotations
	if encLayerFinalizer != nil {
		annotations, err := encLayerFinalizer()
		if err != nil {
			return ocispec.Descriptor{}, fmt.Errorf("error getting annotations from encLayer finalizer: %w", err)
		}
		for k, v := range annotations {
			newDesc.Annotations[k] = v
		}
	}
	return newDesc, err
}

func ingestReader(ctx context.Context, cs content.Ingester, ref string, r io.Reader) (digest.Digest, int64, error) {
	cw, err := content.OpenWriter(ctx, cs, content.WithRef(ref))
	if err != nil {
		return "", 0, fmt.Errorf("failed to open writer: %w", err)
	}
	defer cw.Close()

	if _, err := content.CopyReader(cw, r); err != nil {
		return "", 0, fmt.Errorf("copy failed: %w", err)
	}

	st, err := cw.Status()
	if err != nil {
		return "", 0, fmt.Errorf("failed to get state: %w", err)
	}

	if err := cw.Commit(ctx, st.Offset, ""); err != nil {
		if !errdefs.IsAlreadyExists(err) {
			return "", 0, fmt.Errorf("failed commit on ref %q: %w", ref, err)
		}
	}

	return cw.Digest(), st.Offset, nil
}

// Encrypt or decrypt all the Children of a given descriptor
func cryptChildren(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp, thisPlatform *ocispec.Platform) (ocispec.Descriptor, bool, error) {
	children, err := images.Children(ctx, cs, desc)
	if err != nil {
		if errdefs.IsNotFound(err) {
			return desc, false, nil
		}
		return ocispec.Descriptor{}, false, err
	}

	var newLayers []ocispec.Descriptor
	var config ocispec.Descriptor
	modified := false

	for _, child := range children {
		// we only encrypt child layers and have to update their parents if encryption happened
		switch child.MediaType {
		case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
			config = child
		case images.MediaTypeDockerSchema2LayerGzip, images.MediaTypeDockerSchema2Layer,
			ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayer:
			if cryptoOp == cryptoOpEncrypt && lf(child) {
				nl, err := cryptLayer(ctx, cs, child, cc, cryptoOp)
				if err != nil {
					return ocispec.Descriptor{}, false, err
				}
				modified = true
				newLayers = append(newLayers, nl)
			} else {
				newLayers = append(newLayers, child)
			}
		case encocispec.MediaTypeLayerGzipEnc, encocispec.MediaTypeLayerEnc:
			// this one can be decrypted but also its recipients list changed
			if lf(child) {
				nl, err := cryptLayer(ctx, cs, child, cc, cryptoOp)
				if err != nil || cryptoOp == cryptoOpUnwrapOnly {
					return ocispec.Descriptor{}, false, err
				}
				modified = true
				newLayers = append(newLayers, nl)
			} else {
				newLayers = append(newLayers, child)
			}
		case images.MediaTypeDockerSchema2LayerForeign, images.MediaTypeDockerSchema2LayerForeignGzip:
			// never encrypt/decrypt
			newLayers = append(newLayers, child)
		default:
			return ocispec.Descriptor{}, false, fmt.Errorf("bad/unhandled MediaType %s in encryptChildren", child.MediaType)
		}
	}

	if modified && len(newLayers) > 0 {
		newManifest := ocispec.Manifest{
			Versioned: specs.Versioned{
				SchemaVersion: 2,
			},
			Config: config,
			Layers: newLayers,
		}

		mb, err := json.MarshalIndent(newManifest, "", "   ")
		if err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to marshal image: %w", err)
		}

		newDesc := ocispec.Descriptor{
			MediaType: ocispec.MediaTypeImageManifest,
			Size:      int64(len(mb)),
			Digest:    digest.Canonical.FromBytes(mb),
			Platform:  desc.Platform,
		}

		labels := map[string]string{}
		labels["containerd.io/gc.ref.content.0"] = newManifest.Config.Digest.String()
		for i, ch := range newManifest.Layers {
			labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = ch.Digest.String()
		}

		ref := fmt.Sprintf("manifest-%s", newDesc.Digest.String())

		if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to write config: %w", err)
		}
		return newDesc, true, nil
	}

	return desc, modified, nil
}

// cryptManifest encrypts or decrypts the children of a top level manifest
func cryptManifest(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) {
	p, err := content.ReadBlob(ctx, cs, desc)
	if err != nil {
		return ocispec.Descriptor{}, false, err
	}
	var manifest ocispec.Manifest
	if err := json.Unmarshal(p, &manifest); err != nil {
		return ocispec.Descriptor{}, false, err
	}
	platform := platforms.DefaultSpec()
	newDesc, modified, err := cryptChildren(ctx, cs, desc, cc, lf, cryptoOp, &platform)
	if err != nil || cryptoOp == cryptoOpUnwrapOnly {
		return ocispec.Descriptor{}, false, err
	}
	return newDesc, modified, nil
}

// cryptManifestList encrypts or decrypts the children of a top level manifest list
func cryptManifestList(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) {
	// read the index; if any layer is encrypted and any manifests change we will need to rewrite it
	b, err := content.ReadBlob(ctx, cs, desc)
	if err != nil {
		return ocispec.Descriptor{}, false, err
	}

	var index ocispec.Index
	if err := json.Unmarshal(b, &index); err != nil {
		return ocispec.Descriptor{}, false, err
	}

	var newManifests []ocispec.Descriptor
	modified := false
	for _, manifest := range index.Manifests {
		newManifest, m, err := cryptChildren(ctx, cs, manifest, cc, lf, cryptoOp, manifest.Platform)
		if err != nil || cryptoOp == cryptoOpUnwrapOnly {
			return ocispec.Descriptor{}, false, err
		}
		if m {
			modified = true
		}
		newManifests = append(newManifests, newManifest)
	}

	if modified {
		// we need to update the index
		newIndex := ocispec.Index{
			Versioned: index.Versioned,
			Manifests: newManifests,
		}

		mb, err := json.MarshalIndent(newIndex, "", "   ")
		if err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to marshal index: %w", err)
		}

		newDesc := ocispec.Descriptor{
			MediaType: ocispec.MediaTypeImageIndex,
			Size:      int64(len(mb)),
			Digest:    digest.Canonical.FromBytes(mb),
		}

		labels := map[string]string{}
		for i, m := range newIndex.Manifests {
			labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String()
		}

		ref := fmt.Sprintf("index-%s", newDesc.Digest.String())

		if err = content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to write index: %w", err)
		}
		return newDesc, true, nil
	}

	return desc, false, nil
}

// IsEncryptedDiff returns true if mediaType is a known encrypted media type.
func IsEncryptedDiff(ctx context.Context, mediaType string) bool {
	switch mediaType {
	case encocispec.MediaTypeLayerGzipEnc, encocispec.MediaTypeLayerEnc:
		return true
	}
	return false
}

// HasEncryptedLayer returns true if any LayerInfo indicates that the layer is encrypted
func HasEncryptedLayer(ctx context.Context, layerInfos []ocispec.Descriptor) bool {
	for i := 0; i < len(layerInfos); i++ {
		if IsEncryptedDiff(ctx, layerInfos[i].MediaType) {
			return true
		}
	}
	return false
}

// encryptLayer encrypts the layer using the CryptoConfig and creates a new OCI Descriptor.
// A call to this function may also only manipulate the wrapped keys list.
// The caller is expected to store the returned encrypted data and OCI Descriptor
func encryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor) (ocispec.Descriptor, io.Reader, ocicrypt.EncryptLayerFinalizer, error) {
	var (
		size int64
		d    digest.Digest
		err  error
	)

	encLayerReader, encLayerFinalizer, err := ocicrypt.EncryptLayer(cc.EncryptConfig, ocicrypt.ReaderFromReaderAt(dataReader), desc)
	if err != nil {
		return ocispec.Descriptor{}, nil, nil, err
	}

	// were data touched ?
	if encLayerReader != nil {
		size = 0
		d = ""
	} else {
		size = desc.Size
		d = desc.Digest
	}

	newDesc := ocispec.Descriptor{
		Digest:   d,
		Size:     size,
		Platform: desc.Platform,
	}

	switch desc.MediaType {
	case images.MediaTypeDockerSchema2LayerGzip:
		newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
	case images.MediaTypeDockerSchema2Layer:
		newDesc.MediaType = encocispec.MediaTypeLayerEnc
	case encocispec.MediaTypeLayerGzipEnc:
		newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
	case encocispec.MediaTypeLayerEnc:
		newDesc.MediaType = encocispec.MediaTypeLayerEnc

	// TODO: Mediatypes to be added in ocispec
	case ocispec.MediaTypeImageLayerGzip:
		newDesc.MediaType = encocispec.MediaTypeLayerGzipEnc
	case ocispec.MediaTypeImageLayer:
		newDesc.MediaType = encocispec.MediaTypeLayerEnc

	default:
		return ocispec.Descriptor{}, nil, nil, fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
	}

	return newDesc, encLayerReader, encLayerFinalizer, nil
}

// DecryptLayer decrypts the layer using the DecryptConfig and creates a new OCI Descriptor.
// The caller is expected to store the returned plain data and OCI Descriptor
func DecryptLayer(dc *encconfig.DecryptConfig, dataReader io.Reader, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, digest.Digest, error) {
	resultReader, layerDigest, err := ocicrypt.DecryptLayer(dc, dataReader, desc, unwrapOnly)
	if err != nil || unwrapOnly {
		return ocispec.Descriptor{}, nil, "", err
	}

	newDesc := ocispec.Descriptor{
		Size:     0,
		Platform: desc.Platform,
	}

	switch desc.MediaType {
	case encocispec.MediaTypeLayerGzipEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzip
	case encocispec.MediaTypeLayerEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2Layer
	default:
		return ocispec.Descriptor{}, nil, "", fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
	}
	return newDesc, resultReader, layerDigest, nil
}

// decryptLayer decrypts the layer using the CryptoConfig and creates a new OCI Descriptor.
// The caller is expected to store the returned plain data and OCI Descriptor
func decryptLayer(cc *encconfig.CryptoConfig, dataReader content.ReaderAt, desc ocispec.Descriptor, unwrapOnly bool) (ocispec.Descriptor, io.Reader, error) {
	resultReader, d, err := ocicrypt.DecryptLayer(cc.DecryptConfig, ocicrypt.ReaderFromReaderAt(dataReader), desc, unwrapOnly)
	if err != nil || unwrapOnly {
		return ocispec.Descriptor{}, nil, err
	}

	newDesc := ocispec.Descriptor{
		Digest:   d,
		Size:     0,
		Platform: desc.Platform,
	}

	switch desc.MediaType {
	case encocispec.MediaTypeLayerGzipEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2LayerGzip
	case encocispec.MediaTypeLayerEnc:
		newDesc.MediaType = images.MediaTypeDockerSchema2Layer
	default:
		return ocispec.Descriptor{}, nil, fmt.Errorf("unsupporter layer MediaType: %s", desc.MediaType)
	}
	return newDesc, resultReader, nil
}

// cryptLayer handles the changes due to encryption or decryption of a layer
func cryptLayer(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, cryptoOp cryptoOp) (ocispec.Descriptor, error) {
	var (
		resultReader      io.Reader
		newDesc           ocispec.Descriptor
		encLayerFinalizer ocicrypt.EncryptLayerFinalizer
	)

	dataReader, err := cs.ReaderAt(ctx, desc)
	if err != nil {
		return ocispec.Descriptor{}, err
	}
	defer dataReader.Close()

	if cryptoOp == cryptoOpEncrypt {
		newDesc, resultReader, encLayerFinalizer, err = encryptLayer(cc, dataReader, desc)
	} else {
		newDesc, resultReader, err = decryptLayer(cc, dataReader, desc, cryptoOp == cryptoOpUnwrapOnly)
	}
	if err != nil || cryptoOp == cryptoOpUnwrapOnly {
		return ocispec.Descriptor{}, err
	}

	newDesc.Annotations = ocicrypt.FilterOutAnnotations(desc.Annotations)

	// some operations, such as changing recipients, may not touch the layer at all
	if resultReader != nil {
		var ref string
		// If we have the digest, write blob with checks
		haveDigest := newDesc.Digest.String() != ""
		if haveDigest {
			ref = fmt.Sprintf("layer-%s", newDesc.Digest.String())
		} else {
			ref = fmt.Sprintf("blob-%d-%d", rand.Int(), rand.Int())
		}

		if haveDigest {
			if err := content.WriteBlob(ctx, cs, ref, resultReader, newDesc); err != nil {
				return ocispec.Descriptor{}, fmt.Errorf("failed to write config: %w", err)
			}
		} else {
			newDesc.Digest, newDesc.Size, err = ingestReader(ctx, cs, ref, resultReader)
			if err != nil {
				return ocispec.Descriptor{}, err
			}
		}
	}

	// After performing encryption, call finalizer to get annotations
	if encLayerFinalizer != nil {
		annotations, err := encLayerFinalizer()
		if err != nil {
			return ocispec.Descriptor{}, fmt.Errorf("error getting annotations from encLayer finalizer: %w", err)
		}
		for k, v := range annotations {
			newDesc.Annotations[k] = v
		}
	}
	return newDesc, err
}

func ingestReader(ctx context.Context, cs content.Ingester, ref string, r io.Reader) (digest.Digest, int64, error) {
	cw, err := content.OpenWriter(ctx, cs, content.WithRef(ref))
	if err != nil {
		return "", 0, fmt.Errorf("failed to open writer: %w", err)
	}
	defer cw.Close()

	if _, err := content.CopyReader(cw, r); err != nil {
		return "", 0, fmt.Errorf("copy failed: %w", err)
	}

	st, err := cw.Status()
	if err != nil {
		return "", 0, fmt.Errorf("failed to get state: %w", err)
	}

	if err := cw.Commit(ctx, st.Offset, ""); err != nil {
		if !errdefs.IsAlreadyExists(err) {
			return "", 0, fmt.Errorf("failed commit on ref %q: %w", ref, err)
		}
	}

	return cw.Digest(), st.Offset, nil
}

// Encrypt or decrypt all the Children of a given descriptor
func cryptChildren(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp, thisPlatform *ocispec.Platform) (ocispec.Descriptor, bool, error) {
	children, err := images.Children(ctx, cs, desc)
	if err != nil {
		if errdefs.IsNotFound(err) {
			return desc, false, nil
		}
		return ocispec.Descriptor{}, false, err
	}

	var newLayers []ocispec.Descriptor
	var config ocispec.Descriptor
	modified := false

	for _, child := range children {
		// we only encrypt child layers and have to update their parents if encryption happened
		switch child.MediaType {
		case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
			config = child
		case images.MediaTypeDockerSchema2LayerGzip, images.MediaTypeDockerSchema2Layer,
			ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayer:
			if cryptoOp == cryptoOpEncrypt && lf(child) {
				nl, err := cryptLayer(ctx, cs, child, cc, cryptoOp)
				if err != nil {
					return ocispec.Descriptor{}, false, err
				}
				modified = true
				newLayers = append(newLayers, nl)
			} else {
				newLayers = append(newLayers, child)
			}
		case encocispec.MediaTypeLayerGzipEnc, encocispec.MediaTypeLayerEnc:
			// this one can be decrypted but also its recipients list changed
			if lf(child) {
				nl, err := cryptLayer(ctx, cs, child, cc, cryptoOp)
				if err != nil || cryptoOp == cryptoOpUnwrapOnly {
					return ocispec.Descriptor{}, false, err
				}
				modified = true
				newLayers = append(newLayers, nl)
			} else {
				newLayers = append(newLayers, child)
			}
		case images.MediaTypeDockerSchema2LayerForeign, images.MediaTypeDockerSchema2LayerForeignGzip:
			// never encrypt/decrypt
			newLayers = append(newLayers, child)
		default:
			return ocispec.Descriptor{}, false, fmt.Errorf("bad/unhandled MediaType %s in encryptChildren", child.MediaType)
		}
	}

	if modified && len(newLayers) > 0 {
		newManifest := ocispec.Manifest{
			Versioned: specs.Versioned{
				SchemaVersion: 2,
			},
			Config: config,
			Layers: newLayers,
		}

		mb, err := json.MarshalIndent(newManifest, "", "   ")
		if err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to marshal image: %w", err)
		}

		newDesc := ocispec.Descriptor{
			MediaType: ocispec.MediaTypeImageManifest,
			Size:      int64(len(mb)),
			Digest:    digest.Canonical.FromBytes(mb),
			Platform:  desc.Platform,
		}

		labels := map[string]string{}
		labels["containerd.io/gc.ref.content.0"] = newManifest.Config.Digest.String()
		for i, ch := range newManifest.Layers {
			labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = ch.Digest.String()
		}

		ref := fmt.Sprintf("manifest-%s", newDesc.Digest.String())

		if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to write config: %w", err)
		}
		return newDesc, true, nil
	}

	return desc, modified, nil
}

// cryptManifest encrypts or decrypts the children of a top level manifest
func cryptManifest(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) {
	p, err := content.ReadBlob(ctx, cs, desc)
	if err != nil {
		return ocispec.Descriptor{}, false, err
	}
	var manifest ocispec.Manifest
	if err := json.Unmarshal(p, &manifest); err != nil {
		return ocispec.Descriptor{}, false, err
	}
	platform := platforms.DefaultSpec()
	newDesc, modified, err := cryptChildren(ctx, cs, desc, cc, lf, cryptoOp, &platform)
	if err != nil || cryptoOp == cryptoOpUnwrapOnly {
		return ocispec.Descriptor{}, false, err
	}
	return newDesc, modified, nil
}

// cryptManifestList encrypts or decrypts the children of a top level manifest list
func cryptManifestList(ctx context.Context, cs content.Store, desc ocispec.Descriptor, cc *encconfig.CryptoConfig, lf LayerFilter, cryptoOp cryptoOp) (ocispec.Descriptor, bool, error) {
	// read the index; if any layer is encrypted and any manifests change we will need to rewrite it
	b, err := content.ReadBlob(ctx, cs, desc)
	if err != nil {
		return ocispec.Descriptor{}, false, err
	}

	var index ocispec.Index
	if err := json.Unmarshal(b, &index); err != nil {
		return ocispec.Descriptor{}, false, err
	}

	var newManifests []ocispec.Descriptor
	modified := false
	for _, manifest := range index.Manifests {
		if cryptoOp == cryptoOpUnwrapOnly && !isLocalPlatform(manifest.Platform) {
			continue
		}
		newManifest, m, err := cryptChildren(ctx, cs, manifest, cc, lf, cryptoOp, manifest.Platform)
		if err != nil || cryptoOp == cryptoOpUnwrapOnly {
			return ocispec.Descriptor{}, false, err
		}
		if m {
			modified = true
		}
		newManifests = append(newManifests, newManifest)
	}
	if cryptoOp == cryptoOpUnwrapOnly {
		return ocispec.Descriptor{}, false, fmt.Errorf("No manifest found for local platform")
	}

	if modified {
		// we need to update the index
		newIndex := ocispec.Index{
			Versioned: index.Versioned,
			Manifests: newManifests,
		}

		mb, err := json.MarshalIndent(newIndex, "", "   ")
		if err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to marshal index: %w", err)
		}

		newDesc := ocispec.Descriptor{
			MediaType: ocispec.MediaTypeImageIndex,
			Size:      int64(len(mb)),
			Digest:    digest.Canonical.FromBytes(mb),
		}

		labels := map[string]string{}
		for i, m := range newIndex.Manifests {
			labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = m.Digest.String()
		}

		ref := fmt.Sprintf("index-%s", newDesc.Digest.String())

		if err = content.WriteBlob(ctx, cs, ref, bytes.NewReader(mb), newDesc, content.WithLabels(labels)); err != nil {
			return ocispec.Descriptor{}, false, fmt.Errorf("failed to write index: %w", err)
		}
		return newDesc, true, nil
	}

	return desc, false, nil
}

// isLocalPlatform determines whether the given platform matches the local one
func isLocalPlatform(platform *ocispec.Platform) bool {
	matcher := platforms.NewMatcher(*platform)

	return matcher.Match(platforms.DefaultSpec())
}

--- a/images/encryption/encryption.go
+++ b/images/encryption/encryption.go
@@ -328,6 +328,9 @@
 	var newManifests []ocispec.Descriptor
 	modified := false
 	for _, manifest := range index.Manifests {
+		if cryptoOp == cryptoOpUnwrapOnly && !isLocalPlatform(manifest.Platform) {
+			continue
+		}
 		newManifest, m, err := cryptChildren(ctx, cs, manifest, cc, lf, cryptoOp, manifest.Platform)
 		if err != nil || cryptoOp == cryptoOpUnwrapOnly {
 			return ocispec.Descriptor{}, false, err
@@ -336,6 +339,9 @@
 			modified = true
 		}
 		newManifests = append(newManifests, newManifest)
+	}
+	if cryptoOp == cryptoOpUnwrapOnly {
+		return ocispec.Descriptor{}, false, fmt.Errorf("No manifest found for local platform")
 	}

 	if modified {

--- /dev/null
+++ b/images/encryption/encryption.go
@@ -0,0 +1,6 @@
+// isLocalPlatform determines whether the given platform matches the local one
+func isLocalPlatform(platform *ocispec.Platform) bool {
+	matcher := platforms.NewMatcher(*platform)
+
+	return matcher.Match(platforms.DefaultSpec())
+}


A privilege escalation vulnerability impacting the Google Exposure Notification Verification Server (versions prior to 0.23.1), allows an attacker who (1) has UserWrite permissions and (2) is using a carefully crafted request or malicious proxy, to create another user with higher privileges than their own. This occurs due to insufficient checks on the allowed set of permissions. The new user creation event would be captured in the Event Log.

Incorrect Default Permissions

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

func CompileAndAuthorize(actorPermission Permission, toUpdate []Permission) (Permission, error) {
	var permission Permission
	for _, update := range toUpdate {
		// Verify that the user making changes has the permissions they are trying
		// to grant. It is not valid for someone to grant permissions larger than
		// they currently have.
		if !Can(actorPermission, update) {
			return 0, fmt.Errorf("actor does not have all scopes which are being granted")
		}
		permission = permission | update
	}

	// Ensure implied permissions. The actor must also have the implied
	// permissions by definition.
	permission = AddImplied(permission)
	return permission, nil
}

func CompileAndAuthorize(actorPermission Permission, toUpdate []Permission) (Permission, error) {
	var permission Permission
	for _, update := range toUpdate {
		// Verify the provided permission is a known permission. This prevents a
		// security vulnerability whereby a carefully crafted request is able to
		// provide a value that correctly passes an the bitwise AND check and then
		// modifies the target permission using OR to escalate privilege.
		if _, ok := PermissionMap[update]; !ok {
			if update != LegacyRealmAdmin && update != LegacyRealmUser {
				return 0, fmt.Errorf("provided permission %v is unknown", update)
			}
		}

		// Verify that the user making changes has the permissions they are trying
		// to grant. It is not valid for someone to grant permissions larger than
		// they currently have.
		if !Can(actorPermission, update) {
			return 0, fmt.Errorf("actor does not have all scopes which are being granted")
		}
		permission = permission | update
	}

	// Ensure implied permissions. The actor must also have the implied
	// permissions by definition.
	permission = AddImplied(permission)
	return permission, nil
}

--- a/pkg/rbac/rbac.go
+++ b/pkg/rbac/rbac.go
@@ -1,6 +1,16 @@
 func CompileAndAuthorize(actorPermission Permission, toUpdate []Permission) (Permission, error) {
 	var permission Permission
 	for _, update := range toUpdate {
+		// Verify the provided permission is a known permission. This prevents a
+		// security vulnerability whereby a carefully crafted request is able to
+		// provide a value that correctly passes an the bitwise AND check and then
+		// modifies the target permission using OR to escalate privilege.
+		if _, ok := PermissionMap[update]; !ok {
+			if update != LegacyRealmAdmin && update != LegacyRealmUser {
+				return 0, fmt.Errorf("provided permission %v is unknown", update)
+			}
+		}
+
 		// Verify that the user making changes has the permissions they are trying
 		// to grant. It is not valid for someone to grant permissions larger than
 		// they currently have.

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

cd /workspace/exposure-notifications-verification-server
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^TestRequiredPermissions$/^escalate$ github.com/google/exposure-notifications-verification-server/pkg/rbac

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

cd /workspace/exposure-notifications-verification-server
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestRequiredPermissions|TestImpliedBy|TestPermission_Description|TestPermission_String|TestRBAC_Permissions|TestPermission_Implied|TestPermission_Value|TestPermissionNames)$' github.com/google/exposure-notifications-verification-server/pkg/rbac