Sealos is an open source cloud operating system distribution based on the Kubernetes kernel. In versions of Sealos prior to 4.2.1-rc4 an improper configuration of role based access control (RBAC) permissions resulted in an attacker being able to obtain cluster control permissions, which could control the entire cluster deployed with Sealos, as well as hundreds of pods and other resources within the cluster. This issue has been addressed in version 4.2.1-rc4. Users are advised to upgrade. There are no known workarounds for this vulnerability.

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.

func main() {
	var metricsAddr string
	var enableLeaderElection bool
	var probeAddr string
	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
	flag.BoolVar(&enableLeaderElection, "leader-elect", false,
		"Enable leader election for controller manager. "+
			"Enabling this will ensure there is only one active controller manager.")

	opts := zap.Options{
		Development: true,
	}
	opts.BindFlags(flag.CommandLine)
	flag.Parse()

	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		MetricsBindAddress:     metricsAddr,
		Port:                   9443,
		HealthProbeBindAddress: probeAddr,
		LeaderElection:         enableLeaderElection,
		LeaderElectionID:       "785548a1.sealos.io",
		// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
		// when the Manager ends. This requires the binary to immediately end when the
		// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
		// speeds up voluntary leader transitions as the new leader don't have to wait
		// LeaseDuration time first.
		//
		// In the default scaffold provided, the program ends immediately after
		// the manager stops, so would be fine to enable this option. However,
		// if you are doing or is intended to do any operation such as perform cleanups
		// after the manager stops then its usage might be unsafe.
		// LeaderElectionReleaseOnCancel: true,
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}
	if err = (&controllers.UserReconciler{}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "User")
		os.Exit(1)
	}
	if err = (&controllers.UserGroupReconciler{}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "UserGroup")
		os.Exit(1)
	}
	if err = (&controllers.UserGroupBindingReconciler{
		Client: mgr.GetClient(),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "UserGroupBinding")
		os.Exit(1)
	}

	//if err = (&controllers.UserExpirationReconciler{
	//	Client: mgr.GetClient(),
	//	Scheme: mgr.GetScheme(),
	//}).SetupWithManager(mgr); err != nil {
	//	setupLog.Error(err, "unable to create controller", "controller", "Secret")
	//	os.Exit(1)
	//}

	if err = cache.SetupCache(mgr); err != nil {
		setupLog.Error(err, "unable to cache controller")
		os.Exit(1)
	}
	if os.Getenv("DISABLE_WEBHOOKS") == "true" {
		setupLog.Info("disable all webhooks")
	} else {
		if err = (&userv1.User{}).SetupWebhookWithManager(mgr); err != nil {
			setupLog.Error(err, "unable to create webhook", "webhook", "User")
			os.Exit(1)
		}
		setupLog.Info("add ug and ugb webhooks")
		if err = (&userv1.UserGroup{}).SetupWebhookWithManager(mgr); err != nil {
			setupLog.Error(err, "unable to create webhook", "webhook", "UserGroup")
			os.Exit(1)
		}
		if err = (&userv1.UserGroupBinding{}).SetupWebhookWithManager(mgr); err != nil {
			setupLog.Error(err, "unable to create webhook", "webhook", "UserGroupBinding")
			os.Exit(1)
		}
	}

	//+kubebuilder:scaffold:builder

	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up health check")
		os.Exit(1)
	}
	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up ready check")
		os.Exit(1)
	}
	ctx, cancel := context.WithCancel(context.TODO())
	defer cancel()
	setupLog.Info("starting manager")
	if err = mgr.Start(ctx); err != nil {
		setupLog.Error(err, "failed to running manager")
		os.Exit(1)
	}
}

func main() {
	var metricsAddr string
	var enableLeaderElection bool
	var probeAddr string
	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
	flag.BoolVar(&enableLeaderElection, "leader-elect", false,
		"Enable leader election for controller manager. "+
			"Enabling this will ensure there is only one active controller manager.")

	opts := zap.Options{
		Development: true,
	}
	opts.BindFlags(flag.CommandLine)
	flag.Parse()

	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		MetricsBindAddress:     metricsAddr,
		Port:                   9443,
		HealthProbeBindAddress: probeAddr,
		LeaderElection:         enableLeaderElection,
		LeaderElectionID:       "785548a1.sealos.io",
		// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
		// when the Manager ends. This requires the binary to immediately end when the
		// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
		// speeds up voluntary leader transitions as the new leader don't have to wait
		// LeaseDuration time first.
		//
		// In the default scaffold provided, the program ends immediately after
		// the manager stops, so would be fine to enable this option. However,
		// if you are doing or is intended to do any operation such as perform cleanups
		// after the manager stops then its usage might be unsafe.
		// LeaderElectionReleaseOnCancel: true,
	})
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}
	if err = (&controllers.UserReconciler{}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "User")
		os.Exit(1)
	}
	if err = (&controllers.UserGroupReconciler{}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "UserGroup")
		os.Exit(1)
	}
	if err = (&controllers.UserGroupBindingReconciler{
		Client: mgr.GetClient(),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "UserGroupBinding")
		os.Exit(1)
	}

	if err = (&controllers.NamespaceReconciler{
		Client: mgr.GetClient(),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Namespace")
		os.Exit(1)
	}

	//if err = (&controllers.UserExpirationReconciler{
	//	Client: mgr.GetClient(),
	//	Scheme: mgr.GetScheme(),
	//}).SetupWithManager(mgr); err != nil {
	//	setupLog.Error(err, "unable to create controller", "controller", "Secret")
	//	os.Exit(1)
	//}

	if err = cache.SetupCache(mgr); err != nil {
		setupLog.Error(err, "unable to cache controller")
		os.Exit(1)
	}
	if os.Getenv("DISABLE_WEBHOOKS") == "true" {
		setupLog.Info("disable all webhooks")
	} else {
		if err = (&userv1.User{}).SetupWebhookWithManager(mgr); err != nil {
			setupLog.Error(err, "unable to create webhook", "webhook", "User")
			os.Exit(1)
		}
		setupLog.Info("add ug and ugb webhooks")
		if err = (&userv1.UserGroup{}).SetupWebhookWithManager(mgr); err != nil {
			setupLog.Error(err, "unable to create webhook", "webhook", "UserGroup")
			os.Exit(1)
		}
		if err = (&userv1.UserGroupBinding{}).SetupWebhookWithManager(mgr); err != nil {
			setupLog.Error(err, "unable to create webhook", "webhook", "UserGroupBinding")
			os.Exit(1)
		}
	}

	//+kubebuilder:scaffold:builder

	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up health check")
		os.Exit(1)
	}
	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up ready check")
		os.Exit(1)
	}
	ctx, cancel := context.WithCancel(context.TODO())
	defer cancel()
	setupLog.Info("starting manager")
	if err = mgr.Start(ctx); err != nil {
		setupLog.Error(err, "failed to running manager")
		os.Exit(1)
	}
}

type NamespaceReconciler struct {
	Logger   logr.Logger
	Recorder record.EventRecorder
	*runtime.Scheme
	client.Client
}

func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	r.Logger.V(1).Info("start reconcile for ns")
	ns := &v1.Namespace{}
	if err := r.Get(ctx, req.NamespacedName, ns); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	if strings.HasPrefix(ns.Name, "ns-") {
		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			change, err := controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
				if ns.Labels == nil {
					ns.Labels = map[string]string{}
				}
				ns.Labels = helper.SetPodSecurity(ns.Labels)
				return nil
			})
			if err != nil {
				return err
			}
			r.Logger.V(1).Info("create or update ns", "change", change, "ns", ns.Name)
			return nil
		})
		if err != nil {
			r.Logger.Error(err, "create or update ns error", "ns", ns.Name)
			return ctrl.Result{}, err
		}
	}

	return ctrl.Result{}, nil
}

func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
	const controllerName = "namespace_controller"
	if r.Client == nil {
		r.Client = mgr.GetClient()
	}
	r.Logger = ctrl.Log.WithName(controllerName)
	if r.Recorder == nil {
		r.Recorder = mgr.GetEventRecorderFor(controllerName)
	}
	r.Scheme = mgr.GetScheme()
	r.Logger.V(1).Info("init reconcile controller namespace")
	return ctrl.NewControllerManagedBy(mgr).
		For(&v1.Namespace{}, builder.WithPredicates(
			predicate.Or(predicate.AnnotationChangedPredicate{}, predicate.LabelChangedPredicate{}))).
		Complete(r)
}

--- a/controllers/user/main.go
+++ b/controllers/user/main.go
@@ -55,6 +55,14 @@
 		os.Exit(1)
 	}

+	if err = (&controllers.NamespaceReconciler{
+		Client: mgr.GetClient(),
+		Scheme: mgr.GetScheme(),
+	}).SetupWithManager(mgr); err != nil {
+		setupLog.Error(err, "unable to create controller", "controller", "Namespace")
+		os.Exit(1)
+	}
+
 	//if err = (&controllers.UserExpirationReconciler{
 	//	Client: mgr.GetClient(),
 	//	Scheme: mgr.GetScheme(),

--- /dev/null
+++ b/controllers/user/main.go
@@ -0,0 +1,6 @@
+type NamespaceReconciler struct {
+	Logger   logr.Logger
+	Recorder record.EventRecorder
+	*runtime.Scheme
+	client.Client
+}

--- /dev/null
+++ b/controllers/user/main.go
@@ -0,0 +1,30 @@
+func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	r.Logger.V(1).Info("start reconcile for ns")
+	ns := &v1.Namespace{}
+	if err := r.Get(ctx, req.NamespacedName, ns); err != nil {
+		return ctrl.Result{}, client.IgnoreNotFound(err)
+	}
+
+	if strings.HasPrefix(ns.Name, "ns-") {
+		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
+			change, err := controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
+				if ns.Labels == nil {
+					ns.Labels = map[string]string{}
+				}
+				ns.Labels = helper.SetPodSecurity(ns.Labels)
+				return nil
+			})
+			if err != nil {
+				return err
+			}
+			r.Logger.V(1).Info("create or update ns", "change", change, "ns", ns.Name)
+			return nil
+		})
+		if err != nil {
+			r.Logger.Error(err, "create or update ns error", "ns", ns.Name)
+			return ctrl.Result{}, err
+		}
+	}
+
+	return ctrl.Result{}, nil
+}

--- /dev/null
+++ b/controllers/user/main.go
@@ -0,0 +1,16 @@
+func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	const controllerName = "namespace_controller"
+	if r.Client == nil {
+		r.Client = mgr.GetClient()
+	}
+	r.Logger = ctrl.Log.WithName(controllerName)
+	if r.Recorder == nil {
+		r.Recorder = mgr.GetEventRecorderFor(controllerName)
+	}
+	r.Scheme = mgr.GetScheme()
+	r.Logger.V(1).Info("init reconcile controller namespace")
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&v1.Namespace{}, builder.WithPredicates(
+			predicate.Or(predicate.AnnotationChangedPredicate{}, predicate.LabelChangedPredicate{}))).
+		Complete(r)
+}


CodeChecker is an analyzer tooling, defect database and viewer extension for the Clang Static Analyzer and Clang Tidy. Zip files uploaded to the server endpoint of `CodeChecker store` are not properly sanitized. An attacker, using a path traversal attack, can load and display files on the machine of `CodeChecker server`. The vulnerable endpoint is `/Default/v6.53/CodeCheckerService@massStoreRun`. The path traversal vulnerability allows reading data on the machine of the `CodeChecker server`, with the same permission level as the `CodeChecker server`.
The attack requires a user account on the `CodeChecker server`, with permission to store to a server, and view the stored report. This vulnerability has been patched in version 6.23.

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.

    def __store_source_files(
        self,
        source_root: str,
        filename_to_hash: Dict[str, str]
    ) -> Dict[str, int]:
        """ Storing file contents from plist. """

        file_path_to_id = {}

        for file_name, file_hash in filename_to_hash.items():
            source_file_name = os.path.join(source_root, file_name.strip("/"))
            source_file_name = os.path.realpath(source_file_name)
            LOG.debug("Storing source file: %s", source_file_name)
            trimmed_file_path = trim_path_prefixes(
                file_name, self.__trim_path_prefixes)

            if not os.path.isfile(source_file_name):
                # The file was not in the ZIP file, because we already
                # have the content. Let's check if we already have a file
                # record in the database or we need to add one.

                LOG.debug('%s not found or already stored.', trimmed_file_path)
                with DBSession(self.__Session) as session:
                    fid = add_file_record(
                        session, trimmed_file_path, file_hash)

                if not fid:
                    LOG.error("File ID for %s is not found in the DB with "
                              "content hash %s. Missing from ZIP?",
                              source_file_name, file_hash)
                file_path_to_id[trimmed_file_path] = fid
                LOG.debug("%d fileid found", fid)
                continue

            with DBSession(self.__Session) as session:
                self.__add_file_content(session, source_file_name, file_hash)

                file_path_to_id[trimmed_file_path] = add_file_record(
                    session, trimmed_file_path, file_hash)

        return file_path_to_id

    def __store_source_files(
        self,
        source_root: str,
        filename_to_hash: Dict[str, str]
    ) -> Dict[str, int]:
        """ Storing file contents from plist. """

        file_path_to_id = {}

        for file_name, file_hash in filename_to_hash.items():
            source_file_path = path_for_fake_root(file_name, source_root)
            LOG.debug("Storing source file: %s", source_file_path)
            trimmed_file_path = trim_path_prefixes(
                file_name, self.__trim_path_prefixes)

            if not os.path.isfile(source_file_path):
                # The file was not in the ZIP file, because we already
                # have the content. Let's check if we already have a file
                # record in the database or we need to add one.

                LOG.debug('%s not found or already stored.', trimmed_file_path)
                with DBSession(self.__Session) as session:
                    fid = add_file_record(
                        session, trimmed_file_path, file_hash)

                if fid:
                    file_path_to_id[trimmed_file_path] = fid
                    LOG.debug("%d fileid found", fid)
                else:
                    LOG.error("File ID for %s is not found in the DB with "
                              "content hash %s. Missing from ZIP?",
                              source_file_path, file_hash)
                continue

            with DBSession(self.__Session) as session:
                self.__add_file_content(session, source_file_path, file_hash)

                file_path_to_id[trimmed_file_path] = add_file_record(
                    session, trimmed_file_path, file_hash)

        return file_path_to_id

--- a/web/server/codechecker_server/api/mass_store_run.py
+++ b/web/server/codechecker_server/api/mass_store_run.py
@@ -8,13 +8,12 @@
         file_path_to_id = {}

         for file_name, file_hash in filename_to_hash.items():
-            source_file_name = os.path.join(source_root, file_name.strip("/"))
-            source_file_name = os.path.realpath(source_file_name)
-            LOG.debug("Storing source file: %s", source_file_name)
+            source_file_path = path_for_fake_root(file_name, source_root)
+            LOG.debug("Storing source file: %s", source_file_path)
             trimmed_file_path = trim_path_prefixes(
                 file_name, self.__trim_path_prefixes)

-            if not os.path.isfile(source_file_name):
+            if not os.path.isfile(source_file_path):
                 # The file was not in the ZIP file, because we already
                 # have the content. Let's check if we already have a file
                 # record in the database or we need to add one.
@@ -24,16 +23,17 @@
                     fid = add_file_record(
                         session, trimmed_file_path, file_hash)

-                if not fid:
+                if fid:
+                    file_path_to_id[trimmed_file_path] = fid
+                    LOG.debug("%d fileid found", fid)
+                else:
                     LOG.error("File ID for %s is not found in the DB with "
                               "content hash %s. Missing from ZIP?",
-                              source_file_name, file_hash)
-                file_path_to_id[trimmed_file_path] = fid
-                LOG.debug("%d fileid found", fid)
+                              source_file_path, file_hash)
                 continue

             with DBSession(self.__Session) as session:
-                self.__add_file_content(session, source_file_name, file_hash)
+                self.__add_file_content(session, source_file_path, file_hash)

                 file_path_to_id[trimmed_file_path] = add_file_record(
                     session, trimmed_file_path, file_hash)


Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read arbitrary files via a ..%2f (dot dot encoded slash) in the PATH_INFO to the default URI.

  this.handleNoMatchedRoute = function (method, reqUrl, params, reqObj, respObj) {
    var staticPath
      , controllerInst
      , nonMethodRoutes;

    // Get the path to the file, decoding the request URI
    staticPath = this.config.staticFilePath + decodeURIComponent(reqUrl);
    // Ignore querystring
    staticPath = staticPath.split('?')[0];

    // Static?
    if (utils.file.existsSync(staticPath)) {
      this.handleStaticFile(staticPath, params, reqUrl, reqObj, respObj, params);
    }
    else {
      nonMethodRoutes = this.router.all(reqUrl);

      // Good route, wrong verb -- 405?
      if (nonMethodRoutes.length) {
        this.handleMethodNotAllowed(method, reqUrl, params, reqObj, respObj,
          nonMethodRoutes);
      }
      // Nada, 404
      else {
        this.handleNotFound(reqUrl, params, reqObj, respObj);
      }
    }
  };

  this.handleNoMatchedRoute = function (method, reqUrl, params, reqObj, respObj) {
    var staticPath
      , controllerInst
      , nonMethodRoutes;

    // Get the path to the file, decoding the request URI
    staticPath = path.resolve(path.join(this.config.staticFilePath, decodeURIComponent(reqUrl)));

    // Prevent directory traversal
    if (staticPath.indexOf(this.config.staticFilePath) !== 0) {
      this.handleNotFound(reqUrl, params, reqObj, respObj);
      return;
    }

    // Ignore querystring
    staticPath = staticPath.split('?')[0];

    // Static?
    if (utils.file.existsSync(staticPath)) {
      this.handleStaticFile(staticPath, params, reqUrl, reqObj, respObj, params);
    }
    else {
      nonMethodRoutes = this.router.all(reqUrl);

      // Good route, wrong verb -- 405?
      if (nonMethodRoutes.length) {
        this.handleMethodNotAllowed(method, reqUrl, params, reqObj, respObj,
          nonMethodRoutes);
      }
      // Nada, 404
      else {
        this.handleNotFound(reqUrl, params, reqObj, respObj);
      }
    }
  };

--- a/lib/app/index.js
+++ b/lib/app/index.js
@@ -5,7 +5,14 @@

     // Get the path to the file, decoding the request URI
-    staticPath = this.config.staticFilePath + decodeURIComponent(reqUrl);
+    staticPath = path.resolve(path.join(this.config.staticFilePath, decodeURIComponent(reqUrl)));
+
+    // Prevent directory traversal
+    if (staticPath.indexOf(this.config.staticFilePath) !== 0) {
+      this.handleNotFound(reqUrl, params, reqObj, respObj);
+      return;
+    }
+
     // Ignore querystring
     staticPath = staticPath.split('?')[0];


ORAS is open source software which enables a way to push OCI Artifacts to OCI Conformant registries. ORAS is both a CLI for initial testing and a Go Module. In ORAS from version 0.4.0 and before version 0.9.0, there is a "zip-slip" vulnerability. The directory support feature allows the downloaded gzipped tarballs to be automatically extracted to the user-specified directory where the tarball can have symbolic links and hard links. A well-crafted tarball or tarballs allow malicious artifact providers linking, writing, or overwriting specific files on the host filesystem outside of the user-specified directory unexpectedly with the same permissions as the user who runs `oras pull`. Users of the affected versions are impacted if they are `oras` CLI users who runs `oras pull`, or if they are Go programs, which invoke `github.com/deislabs/oras/pkg/content.FileStore`. The problem has been fixed in version 0.9.0. For `oras` CLI users, there is no workarounds other than pulling from a trusted artifact provider. For `oras` package users, the workaround is to not use `github.com/deislabs/oras/pkg/content.FileStore`, and use other content stores instead, or pull from a trusted artifact provider.

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

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

func extractTarDirectory(root, prefix string, r io.Reader) error {
	tr := tar.NewReader(r)
	for {
		header, err := tr.Next()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		// Name check
		name := header.Name
		path, err := filepath.Rel(prefix, name)
		if err != nil {
			return err
		}
		if strings.HasPrefix(path, "../") {
			return fmt.Errorf("%q does not have prefix %q", name, prefix)
		}
		path = filepath.Join(root, path)

		// Create content
		switch header.Typeflag {
		case tar.TypeReg:
			err = writeFile(path, tr, header.FileInfo().Mode())
		case tar.TypeDir:
			err = os.MkdirAll(path, header.FileInfo().Mode())
		case tar.TypeLink:
			err = os.Link(header.Linkname, path)
		case tar.TypeSymlink:
			err = os.Symlink(header.Linkname, path)
		default:
			continue // Non-regular files are skipped
		}
		if err != nil {
			return err
		}

		// Change access time and modification time if possible (error ignored)
		os.Chtimes(path, header.AccessTime, header.ModTime)
	}
}

func extractTarDirectory(root, prefix string, r io.Reader) error {
	tr := tar.NewReader(r)
	for {
		header, err := tr.Next()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}

		// Name check
		name := header.Name
		path, err := ensureBasePath(root, prefix, name)
		if err != nil {
			return err
		}
		path = filepath.Join(root, path)

		// Link check
		switch header.Typeflag {
		case tar.TypeLink, tar.TypeSymlink:
			link := header.Linkname
			if !filepath.IsAbs(link) {
				link = filepath.Join(filepath.Dir(name), link)
			}
			if _, err := ensureBasePath(root, prefix, link); err != nil {
				return err
			}
		}

		// Create content
		switch header.Typeflag {
		case tar.TypeReg:
			err = writeFile(path, tr, header.FileInfo().Mode())
		case tar.TypeDir:
			err = os.MkdirAll(path, header.FileInfo().Mode())
		case tar.TypeLink:
			err = os.Link(header.Linkname, path)
		case tar.TypeSymlink:
			err = os.Symlink(header.Linkname, path)
		default:
			continue // Non-regular files are skipped
		}
		if err != nil {
			return err
		}

		// Change access time and modification time if possible (error ignored)
		os.Chtimes(path, header.AccessTime, header.ModTime)
	}
}

func ensureBasePath(root, base, target string) (string, error) {
	path, err := filepath.Rel(base, target)
	if err != nil {
		return "", err
	}
	cleanPath := filepath.ToSlash(filepath.Clean(path))
	if cleanPath == ".." || strings.HasPrefix(cleanPath, "../") {
		return "", fmt.Errorf("%q is outside of %q", target, base)
	}

	// No symbolic link allowed in the relative path
	dir := filepath.Dir(path)
	for dir != "." {
		if info, err := os.Lstat(filepath.Join(root, dir)); err != nil {
			if !os.IsNotExist(err) {
				return "", err
			}
		} else if info.Mode()&os.ModeSymlink != 0 {
			return "", fmt.Errorf("no symbolic link allowed between %q and %q", base, target)
		}
		dir = filepath.Dir(dir)
	}

	return path, nil
}

--- a/pkg/content/utils.go
+++ b/pkg/content/utils.go
@@ -11,14 +11,23 @@

 		// Name check
 		name := header.Name
-		path, err := filepath.Rel(prefix, name)
+		path, err := ensureBasePath(root, prefix, name)
 		if err != nil {
 			return err
 		}
-		if strings.HasPrefix(path, "../") {
-			return fmt.Errorf("%q does not have prefix %q", name, prefix)
+		path = filepath.Join(root, path)
+
+		// Link check
+		switch header.Typeflag {
+		case tar.TypeLink, tar.TypeSymlink:
+			link := header.Linkname
+			if !filepath.IsAbs(link) {
+				link = filepath.Join(filepath.Dir(name), link)
+			}
+			if _, err := ensureBasePath(root, prefix, link); err != nil {
+				return err
+			}
 		}
-		path = filepath.Join(root, path)

 		// Create content
 		switch header.Typeflag {

--- /dev/null
+++ b/pkg/content/utils.go
@@ -0,0 +1,25 @@
+func ensureBasePath(root, base, target string) (string, error) {
+	path, err := filepath.Rel(base, target)
+	if err != nil {
+		return "", err
+	}
+	cleanPath := filepath.ToSlash(filepath.Clean(path))
+	if cleanPath == ".." || strings.HasPrefix(cleanPath, "../") {
+		return "", fmt.Errorf("%q is outside of %q", target, base)
+	}
+
+	// No symbolic link allowed in the relative path
+	dir := filepath.Dir(path)
+	for dir != "." {
+		if info, err := os.Lstat(filepath.Join(root, dir)); err != nil {
+			if !os.IsNotExist(err) {
+				return "", err
+			}
+		} else if info.Mode()&os.ModeSymlink != 0 {
+			return "", fmt.Errorf("no symbolic link allowed between %q and %q", base, target)
+		}
+		dir = filepath.Dir(dir)
+	}
+
+	return path, nil
+}


Improper Privilege Management in GitHub repository usememos/memos prior to 0.13.2.

Improper Authorization

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

Execution with Unnecessary Privileges

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

Improper Privilege Management

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

func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
	return func(c echo.Context) error {
		path := c.Request().URL.Path
		method := c.Request().Method

		if server.defaultAuthSkipper(c) {
			return next(c)
		}

		// Skip validation for server status endpoints.
		if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet {
			return next(c)
		}

		token := findAccessToken(c)
		if token == "" {
			// Allow the user to access the public endpoints.
			if common.HasPrefixes(path, "/o") {
				return next(c)
			}
			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
			if common.HasPrefixes(path, "/api/status", "/api/memo") && method == http.MethodGet {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
		}

		claims := &Claims{}
		accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
			if t.Method.Alg() != jwt.SigningMethodHS256.Name {
				return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
			}
			if kid, ok := t.Header["kid"].(string); ok {
				if kid == "v1" {
					return []byte(secret), nil
				}
			}
			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
		})
		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
		}
		generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
		if err != nil {
			var ve *jwt.ValidationError
			if errors.As(err, &ve) {
				// If expiration error is the only error, we will clear the err
				// and generate new access token and refresh token
				if ve.Errors == jwt.ValidationErrorExpired {
					generateToken = true
				}
			} else {
				return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
			}
		}

		// We either have a valid access token or we will attempt to generate new access token and refresh token
		ctx := c.Request().Context()
		userID, err := strconv.Atoi(claims.Subject)
		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
		}

		// Even if there is no error, we still need to make sure the user still exists.
		user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
			ID: &userID,
		})
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
		}
		if user == nil {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
		}

		if generateToken {
			generateTokenFunc := func() error {
				rc, err := c.Cookie(auth.RefreshTokenCookieName)
				if err != nil {
					return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
				}

				// Parses token and checks if it's valid.
				refreshTokenClaims := &Claims{}
				refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
					if t.Method.Alg() != jwt.SigningMethodHS256.Name {
						return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
					}

					if kid, ok := t.Header["kid"].(string); ok {
						if kid == "v1" {
							return []byte(secret), nil
						}
					}
					return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
				})
				if err != nil {
					if err == jwt.ErrSignatureInvalid {
						return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
					}
					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
				}

				if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
					return echo.NewHTTPError(http.StatusUnauthorized,
						fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
							refreshTokenClaims.Audience,
							auth.RefreshTokenAudienceName,
						))
				}

				// If we have a valid refresh token, we will generate new access token and refresh token
				if refreshToken != nil && refreshToken.Valid {
					if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
						return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
					}
				}

				return nil
			}

			// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
			// In such case, we won't return the error.
			if err := generateTokenFunc(); err != nil && !accessToken.Valid {
				return err
			}
		}

		// Stores userID into context.
		c.Set(getUserIDContextKey(), userID)
		return next(c)
	}
}

func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
	return func(c echo.Context) error {
		path := c.Request().URL.Path
		method := c.Request().Method

		if server.defaultAuthSkipper(c) {
			return next(c)
		}

		// Skip validation for server status endpoints.
		if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet {
			return next(c)
		}

		token := findAccessToken(c)
		if token == "" {
			// Allow the user to access the public endpoints.
			if common.HasPrefixes(path, "/o") {
				return next(c)
			}
			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
			if common.HasPrefixes(path, "/api/status", "/api/memo") && method == http.MethodGet {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
		}

		claims := &Claims{}
		accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
			if t.Method.Alg() != jwt.SigningMethodHS256.Name {
				return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
			}
			if kid, ok := t.Header["kid"].(string); ok {
				if kid == "v1" {
					return []byte(secret), nil
				}
			}
			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
		})

		if !accessToken.Valid {
			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
		}

		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
		}
		generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
		if err != nil {
			var ve *jwt.ValidationError
			if errors.As(err, &ve) {
				// If expiration error is the only error, we will clear the err
				// and generate new access token and refresh token
				if ve.Errors == jwt.ValidationErrorExpired {
					generateToken = true
				}
			} else {
				return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
			}
		}

		// We either have a valid access token or we will attempt to generate new access token and refresh token
		ctx := c.Request().Context()
		userID, err := strconv.Atoi(claims.Subject)
		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
		}

		// Even if there is no error, we still need to make sure the user still exists.
		user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
			ID: &userID,
		})
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
		}
		if user == nil {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
		}

		if generateToken {
			generateTokenFunc := func() error {
				rc, err := c.Cookie(auth.RefreshTokenCookieName)
				if err != nil {
					return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
				}

				// Parses token and checks if it's valid.
				refreshTokenClaims := &Claims{}
				refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
					if t.Method.Alg() != jwt.SigningMethodHS256.Name {
						return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
					}

					if kid, ok := t.Header["kid"].(string); ok {
						if kid == "v1" {
							return []byte(secret), nil
						}
					}
					return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
				})
				if err != nil {
					if err == jwt.ErrSignatureInvalid {
						return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
					}
					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
				}

				if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
					return echo.NewHTTPError(http.StatusUnauthorized,
						fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
							refreshTokenClaims.Audience,
							auth.RefreshTokenAudienceName,
						))
				}

				// If we have a valid refresh token, we will generate new access token and refresh token
				if refreshToken != nil && refreshToken.Valid {
					if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
						return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
					}
				}

				return nil
			}

			// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
			// In such case, we won't return the error.
			if err := generateTokenFunc(); err != nil && !accessToken.Valid {
				return err
			}
		}

		// Stores userID into context.
		c.Set(getUserIDContextKey(), userID)
		return next(c)
	}
}

--- a/server/jwt.go
+++ b/server/jwt.go
@@ -37,6 +37,11 @@
 			}
 			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
 		})
+
+		if !accessToken.Valid {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
+		}
+
 		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
 			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
 		}


Improper Privilege Management in GitHub repository octoprint/octoprint prior to 1.8.3.

    def get_additional_permissions(self):
        return [
            {
                "key": "LIST",
                "name": "List plugins",
                "description": gettext("Allows to list installed plugins."),
                "default_groups": [READONLY_GROUP, USER_GROUP, ADMIN_GROUP],
                "roles": ["manage"],
            },
            {
                "key": "MANAGE",
                "name": "Manage plugins",
                "description": gettext(
                    "Allows to enable, disable and uninstall installed plugins."
                ),
                "default_groups": [ADMIN_GROUP],
                "roles": ["manage"],
            },
            {
                "key": "INSTALL",
                "name": "Install new plugins",
                "description": gettext(
                    'Allows to install new plugins. Includes the "Manage plugins" permission.'
                ),
                "default_groups": [ADMIN_GROUP],
                "roles": ["install"],
                "permissions": ["PLUGIN_PLUGINMANAGER_MANAGE"],
                "dangerous": True,
            },
        ]

    def get_additional_permissions(self):
        return [
            {
                "key": "LIST",
                "name": "List plugins",
                "description": gettext("Allows to list installed plugins."),
                "default_groups": [READONLY_GROUP, USER_GROUP, ADMIN_GROUP],
                "roles": ["list"],
            },
            {
                "key": "MANAGE",
                "name": "Manage plugins",
                "description": gettext(
                    "Allows to enable, disable and uninstall installed plugins."
                ),
                "default_groups": [ADMIN_GROUP],
                "roles": ["manage"],
            },
            {
                "key": "INSTALL",
                "name": "Install new plugins",
                "description": gettext(
                    'Allows to install new plugins. Includes the "Manage plugins" permission.'
                ),
                "default_groups": [ADMIN_GROUP],
                "roles": ["install"],
                "permissions": ["PLUGIN_PLUGINMANAGER_MANAGE"],
                "dangerous": True,
            },
        ]

--- a/src/octoprint/plugins/pluginmanager/__init__.py
+++ b/src/octoprint/plugins/pluginmanager/__init__.py
@@ -5,7 +5,7 @@
                 "name": "List plugins",
                 "description": gettext("Allows to list installed plugins."),
                 "default_groups": [READONLY_GROUP, USER_GROUP, ADMIN_GROUP],
-                "roles": ["manage"],
+                "roles": ["list"],
             },
             {
                 "key": "MANAGE",


Storage.save in Django 2.2 before 2.2.26, 3.2 before 3.2.11, and 4.0 before 4.0.1 allows directory traversal if crafted filenames are directly passed to it.

    def save(self, name, content, max_length=None):
        """
        Save new content to the file specified by name. The content should be
        a proper File object or any Python file-like object, ready to be read
        from the beginning.
        """
        # Get the proper name for the file, as it will actually be saved.
        if name is None:
            name = content.name

        if not hasattr(content, 'chunks'):
            content = File(content, name)

        name = self.get_available_name(name, max_length=max_length)
        return self._save(name, content)

    def get_available_name(self, name, max_length=None):
        """
        Return a filename that's free on the target storage system and
        available for new content to be written to.
        """
        dir_name, file_name = os.path.split(name)
        if '..' in pathlib.PurePath(dir_name).parts:
            raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
        validate_file_name(file_name)
        file_root, file_ext = os.path.splitext(file_name)
        # If the filename already exists, add an underscore and a random 7
        # character alphanumeric string (before the file extension, if one
        # exists) to the filename until the generated filename doesn't exist.
        # Truncate original name if required, so the new filename does not
        # exceed the max_length.
        while self.exists(name) or (max_length and len(name) > max_length):
            # file_ext includes the dot.
            name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
            if max_length is None:
                continue
            # Truncate file_root if max_length exceeded.
            truncation = len(name) - max_length
            if truncation > 0:
                file_root = file_root[:-truncation]
                # Entire file_root was truncated in attempt to find an available filename.
                if not file_root:
                    raise SuspiciousFileOperation(
                        'Storage can not find an available filename for "%s". '
                        'Please make sure that the corresponding file field '
                        'allows sufficient "max_length".' % name
                    )
                name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
        return name

    def generate_filename(self, filename):
        """
        Validate the filename by calling get_valid_name() and return a filename
        to be passed to the save() method.
        """
        # `filename` may include a path as returned by FileField.upload_to.
        dirname, filename = os.path.split(filename)
        if '..' in pathlib.PurePath(dirname).parts:
            raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dirname)
        return os.path.normpath(os.path.join(dirname, self.get_valid_name(filename)))

    def _save(self, name, content):
        full_path = self.path(name)

        # Create any intermediate directories that do not exist.
        directory = os.path.dirname(full_path)
        if not os.path.exists(directory):
            try:
                if self.directory_permissions_mode is not None:
                    # Set the umask because os.makedirs() doesn't apply the "mode"
                    # argument to intermediate-level directories.
                    old_umask = os.umask(0o777 & ~self.directory_permissions_mode)
                    try:
                        os.makedirs(directory, self.directory_permissions_mode)
                    finally:
                        os.umask(old_umask)
                else:
                    os.makedirs(directory)
            except FileExistsError:
                # There's a race between os.path.exists() and os.makedirs().
                # If os.makedirs() fails with FileExistsError, the directory
                # was created concurrently.
                pass
        if not os.path.isdir(directory):
            raise IOError("%s exists and is not a directory." % directory)

        # There's a potential race condition between get_available_name and
        # saving the file; it's possible that two threads might return the
        # same name, at which point all sorts of fun happens. So we need to
        # try to create the file, but if it already exists we have to go back
        # to get_available_name() and try again.

        while True:
            try:
                # This file has a file path that we can move.
                if hasattr(content, 'temporary_file_path'):
                    file_move_safe(content.temporary_file_path(), full_path)

                # This is a normal uploadedfile that we can stream.
                else:
                    # The current umask value is masked out by os.open!
                    fd = os.open(full_path, self.OS_OPEN_FLAGS, 0o666)
                    _file = None
                    try:
                        locks.lock(fd, locks.LOCK_EX)
                        for chunk in content.chunks():
                            if _file is None:
                                mode = 'wb' if isinstance(chunk, bytes) else 'wt'
                                _file = os.fdopen(fd, mode)
                            _file.write(chunk)
                    finally:
                        locks.unlock(fd)
                        if _file is not None:
                            _file.close()
                        else:
                            os.close(fd)
            except FileExistsError:
                # A new name is needed if the file exists.
                name = self.get_available_name(name)
                full_path = self.path(name)
            else:
                # OK, the file save worked. Break out of the loop.
                break

        if self.file_permissions_mode is not None:
            os.chmod(full_path, self.file_permissions_mode)

        # Store filenames with forward slashes, even on Windows.
        return name.replace('\\', '/')

    def save(self, name, content, max_length=None):
        """
        Save new content to the file specified by name. The content should be
        a proper File object or any Python file-like object, ready to be read
        from the beginning.
        """
        # Get the proper name for the file, as it will actually be saved.
        if name is None:
            name = content.name

        if not hasattr(content, 'chunks'):
            content = File(content, name)

        name = self.get_available_name(name, max_length=max_length)
        name = self._save(name, content)
        # Ensure that the name returned from the storage system is still valid.
        validate_file_name(name, allow_relative_path=True)
        return name

    def get_available_name(self, name, max_length=None):
        """
        Return a filename that's free on the target storage system and
        available for new content to be written to.
        """
        name = str(name).replace('\\', '/')
        dir_name, file_name = os.path.split(name)
        if '..' in pathlib.PurePath(dir_name).parts:
            raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)
        validate_file_name(file_name)
        file_root, file_ext = os.path.splitext(file_name)
        # If the filename already exists, add an underscore and a random 7
        # character alphanumeric string (before the file extension, if one
        # exists) to the filename until the generated filename doesn't exist.
        # Truncate original name if required, so the new filename does not
        # exceed the max_length.
        while self.exists(name) or (max_length and len(name) > max_length):
            # file_ext includes the dot.
            name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
            if max_length is None:
                continue
            # Truncate file_root if max_length exceeded.
            truncation = len(name) - max_length
            if truncation > 0:
                file_root = file_root[:-truncation]
                # Entire file_root was truncated in attempt to find an available filename.
                if not file_root:
                    raise SuspiciousFileOperation(
                        'Storage can not find an available filename for "%s". '
                        'Please make sure that the corresponding file field '
                        'allows sufficient "max_length".' % name
                    )
                name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
        return name

    def generate_filename(self, filename):
        """
        Validate the filename by calling get_valid_name() and return a filename
        to be passed to the save() method.
        """
        filename = str(filename).replace('\\', '/')
        # `filename` may include a path as returned by FileField.upload_to.
        dirname, filename = os.path.split(filename)
        if '..' in pathlib.PurePath(dirname).parts:
            raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dirname)
        return os.path.normpath(os.path.join(dirname, self.get_valid_name(filename)))

    def _save(self, name, content):
        full_path = self.path(name)

        # Create any intermediate directories that do not exist.
        directory = os.path.dirname(full_path)
        if not os.path.exists(directory):
            try:
                if self.directory_permissions_mode is not None:
                    # Set the umask because os.makedirs() doesn't apply the "mode"
                    # argument to intermediate-level directories.
                    old_umask = os.umask(0o777 & ~self.directory_permissions_mode)
                    try:
                        os.makedirs(directory, self.directory_permissions_mode)
                    finally:
                        os.umask(old_umask)
                else:
                    os.makedirs(directory)
            except FileExistsError:
                # There's a race between os.path.exists() and os.makedirs().
                # If os.makedirs() fails with FileExistsError, the directory
                # was created concurrently.
                pass
        if not os.path.isdir(directory):
            raise IOError("%s exists and is not a directory." % directory)

        # There's a potential race condition between get_available_name and
        # saving the file; it's possible that two threads might return the
        # same name, at which point all sorts of fun happens. So we need to
        # try to create the file, but if it already exists we have to go back
        # to get_available_name() and try again.

        while True:
            try:
                # This file has a file path that we can move.
                if hasattr(content, 'temporary_file_path'):
                    file_move_safe(content.temporary_file_path(), full_path)

                # This is a normal uploadedfile that we can stream.
                else:
                    # The current umask value is masked out by os.open!
                    fd = os.open(full_path, self.OS_OPEN_FLAGS, 0o666)
                    _file = None
                    try:
                        locks.lock(fd, locks.LOCK_EX)
                        for chunk in content.chunks():
                            if _file is None:
                                mode = 'wb' if isinstance(chunk, bytes) else 'wt'
                                _file = os.fdopen(fd, mode)
                            _file.write(chunk)
                    finally:
                        locks.unlock(fd)
                        if _file is not None:
                            _file.close()
                        else:
                            os.close(fd)
            except FileExistsError:
                # A new name is needed if the file exists.
                name = self.get_available_name(name)
                full_path = self.path(name)
            else:
                # OK, the file save worked. Break out of the loop.
                break

        if self.file_permissions_mode is not None:
            os.chmod(full_path, self.file_permissions_mode)

        # Ensure the saved path is always relative to the storage root.
        name = os.path.relpath(full_path, self.location)
        # Store filenames with forward slashes, even on Windows.
        return name.replace('\\', '/')

--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -12,4 +12,7 @@
             content = File(content, name)

         name = self.get_available_name(name, max_length=max_length)
-        return self._save(name, content)
+        name = self._save(name, content)
+        # Ensure that the name returned from the storage system is still valid.
+        validate_file_name(name, allow_relative_path=True)
+        return name

--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -3,6 +3,7 @@
         Return a filename that's free on the target storage system and
         available for new content to be written to.
         """
+        name = str(name).replace('\\', '/')
         dir_name, file_name = os.path.split(name)
         if '..' in pathlib.PurePath(dir_name).parts:
             raise SuspiciousFileOperation("Detected path traversal attempt in '%s'" % dir_name)

--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -3,6 +3,7 @@
         Validate the filename by calling get_valid_name() and return a filename
         to be passed to the save() method.
         """
+        filename = str(filename).replace('\\', '/')
         # `filename` may include a path as returned by FileField.upload_to.
         dirname, filename = os.path.split(filename)
         if '..' in pathlib.PurePath(dirname).parts:

--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -64,5 +64,7 @@
         if self.file_permissions_mode is not None:
             os.chmod(full_path, self.file_permissions_mode)

+        # Ensure the saved path is always relative to the storage root.
+        name = os.path.relpath(full_path, self.location)
         # Store filenames with forward slashes, even on Windows.
         return name.replace('\\', '/')

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2021-45452: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-2021-45452/bin/python ./runtests.py file_storage.test_generate_filename.GenerateFilenameStorageTests.test_storage_dangerous_paths_dir_name file_storage.tests.FileStorageTests.test_file_save_abs_path

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

cd /workspace/django
git apply --whitespace=nowarn /workspace/fix.patch
cd tests && /workspace/PoC_env/CVE-2021-45452/bin/python ./runtests.py file_storage.test_generate_filename file_storage.tests


diskusage-ng through 0.2.4 is vulnerable to Command Injection.It allows execution of arbitrary commands via the path argument.

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.

function diskusage(path, cb) {
    if (path.indexOf('"') !== -1) {
        return cb(new Error('Paths with double quotes are not supported yet'));
    }

    exec('df -k "' + path + '"', function(err, stdout) {
        if (err) {
            return cb(err);
        }

        try {
            cb(null, parse(stdout));
        } catch (e) {
            cb(e);
        }
    });
}

function diskusage(path, cb) {
    execFile('df', ['-k', path], function(err, stdout) {
        if (err) {
            return cb(err);
        }

        try {
            cb(null, parse(stdout));
        } catch (e) {
            cb(e);
        }
    });
}

--- a/lib/posix.js
+++ b/lib/posix.js
@@ -1,9 +1,5 @@
 function diskusage(path, cb) {
-    if (path.indexOf('"') !== -1) {
-        return cb(new Error('Paths with double quotes are not supported yet'));
-    }
-
-    exec('df -k "' + path + '"', function(err, stdout) {
+    execFile('df', ['-k', path], function(err, stdout) {
         if (err) {
             return cb(err);
         }

--- /dev/null
+++ b/lib/posix.js
@@ -0,0 +1 @@
+var execFile = require('child_process').execFile;

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

cd /workspace/node-diskusage-ng
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
jest ./poc


Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Parse Server crashes when uploading a file without extension. This vulnerability has been patched in versions 5.5.6 and 6.3.1.

  async createHandler(req, res, next) {
    const config = req.config;
    const user = req.auth.user;
    const isMaster = req.auth.isMaster;
    const isLinked = user && Parse.AnonymousUtils.isLinked(user);
    if (!isMaster && !config.fileUpload.enableForAnonymousUser && isLinked) {
      next(
        new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.')
      );
      return;
    }
    if (!isMaster && !config.fileUpload.enableForAuthenticatedUser && !isLinked && user) {
      next(
        new Parse.Error(
          Parse.Error.FILE_SAVE_ERROR,
          'File upload by authenticated user is disabled.'
        )
      );
      return;
    }
    if (!isMaster && !config.fileUpload.enableForPublic && !user) {
      next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.'));
      return;
    }
    const filesController = config.filesController;
    const { filename } = req.params;
    const contentType = req.get('Content-type');

    if (!req.body || !req.body.length) {
      next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.'));
      return;
    }

    const error = filesController.validateFilename(filename);
    if (error) {
      next(error);
      return;
    }

    const fileExtensions = config.fileUpload?.fileExtensions;
    if (!isMaster && fileExtensions) {
      const isValidExtension = extension => {
        return fileExtensions.some(ext => {
          if (ext === '*') {
            return true;
          }
          const regex = new RegExp(fileExtensions);
          if (regex.test(extension)) {
            return true;
          }
        });
      };
      let extension = contentType;
      if (filename && filename.includes('.')) {
        extension = filename.split('.')[1];
      } else if (contentType && contentType.includes('/')) {
        extension = contentType.split('/')[1];
      }
      extension = extension.split(' ').join('');

      if (!isValidExtension(extension)) {
        next(
          new Parse.Error(
            Parse.Error.FILE_SAVE_ERROR,
            `File upload of extension ${extension} is disabled.`
          )
        );
        return;
      }
    }

    const base64 = req.body.toString('base64');
    const file = new Parse.File(filename, { base64 }, contentType);
    const { metadata = {}, tags = {} } = req.fileData || {};
    try {
      // Scan request data for denied keywords
      Utils.checkProhibitedKeywords(config, metadata);
      Utils.checkProhibitedKeywords(config, tags);
    } catch (error) {
      next(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
      return;
    }
    file.setTags(tags);
    file.setMetadata(metadata);
    const fileSize = Buffer.byteLength(req.body);
    const fileObject = { file, fileSize };
    try {
      // run beforeSaveFile trigger
      const triggerResult = await triggers.maybeRunFileTrigger(
        triggers.Types.beforeSave,
        fileObject,
        config,
        req.auth
      );
      let saveResult;
      // if a new ParseFile is returned check if it's an already saved file
      if (triggerResult instanceof Parse.File) {
        fileObject.file = triggerResult;
        if (triggerResult.url()) {
          // set fileSize to null because we wont know how big it is here
          fileObject.fileSize = null;
          saveResult = {
            url: triggerResult.url(),
            name: triggerResult._name,
          };
        }
      }
      // if the file returned by the trigger has already been saved skip saving anything
      if (!saveResult) {
        // if the ParseFile returned is type uri, download the file before saving it
        await addFileDataIfNeeded(fileObject.file);
        // update fileSize
        const bufferData = Buffer.from(fileObject.file._data, 'base64');
        fileObject.fileSize = Buffer.byteLength(bufferData);
        // prepare file options
        const fileOptions = {
          metadata: fileObject.file._metadata,
        };
        // some s3-compatible providers (DigitalOcean, Linode) do not accept tags
        // so we do not include the tags option if it is empty.
        const fileTags =
          Object.keys(fileObject.file._tags).length > 0 ? { tags: fileObject.file._tags } : {};
        Object.assign(fileOptions, fileTags);
        // save file
        const createFileResult = await filesController.createFile(
          config,
          fileObject.file._name,
          bufferData,
          fileObject.file._source.type,
          fileOptions
        );
        // update file with new data
        fileObject.file._name = createFileResult.name;
        fileObject.file._url = createFileResult.url;
        fileObject.file._requestTask = null;
        fileObject.file._previousSave = Promise.resolve(fileObject.file);
        saveResult = {
          url: createFileResult.url,
          name: createFileResult.name,
        };
      }
      // run afterSaveFile trigger
      await triggers.maybeRunFileTrigger(triggers.Types.afterSave, fileObject, config, req.auth);
      res.status(201);
      res.set('Location', saveResult.url);
      res.json(saveResult);
    } catch (e) {
      logger.error('Error creating a file: ', e);
      const error = triggers.resolveError(e, {
        code: Parse.Error.FILE_SAVE_ERROR,
        message: `Could not store file: ${fileObject.file._name}.`,
      });
      next(error);
    }
  }

  async createHandler(req, res, next) {
    const config = req.config;
    const user = req.auth.user;
    const isMaster = req.auth.isMaster;
    const isLinked = user && Parse.AnonymousUtils.isLinked(user);
    if (!isMaster && !config.fileUpload.enableForAnonymousUser && isLinked) {
      next(
        new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by anonymous user is disabled.')
      );
      return;
    }
    if (!isMaster && !config.fileUpload.enableForAuthenticatedUser && !isLinked && user) {
      next(
        new Parse.Error(
          Parse.Error.FILE_SAVE_ERROR,
          'File upload by authenticated user is disabled.'
        )
      );
      return;
    }
    if (!isMaster && !config.fileUpload.enableForPublic && !user) {
      next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'File upload by public is disabled.'));
      return;
    }
    const filesController = config.filesController;
    const { filename } = req.params;
    const contentType = req.get('Content-type');

    if (!req.body || !req.body.length) {
      next(new Parse.Error(Parse.Error.FILE_SAVE_ERROR, 'Invalid file upload.'));
      return;
    }

    const error = filesController.validateFilename(filename);
    if (error) {
      next(error);
      return;
    }

    const fileExtensions = config.fileUpload?.fileExtensions;
    if (!isMaster && fileExtensions) {
      const isValidExtension = extension => {
        return fileExtensions.some(ext => {
          if (ext === '*') {
            return true;
          }
          const regex = new RegExp(fileExtensions);
          if (regex.test(extension)) {
            return true;
          }
        });
      };
      let extension = contentType;
      if (filename && filename.includes('.')) {
        extension = filename.split('.')[1];
      } else if (contentType && contentType.includes('/')) {
        extension = contentType.split('/')[1];
      }
      extension = extension?.split(' ')?.join('');

      if (extension && !isValidExtension(extension)) {
        next(
          new Parse.Error(
            Parse.Error.FILE_SAVE_ERROR,
            `File upload of extension ${extension} is disabled.`
          )
        );
        return;
      }
    }

    const base64 = req.body.toString('base64');
    const file = new Parse.File(filename, { base64 }, contentType);
    const { metadata = {}, tags = {} } = req.fileData || {};
    try {
      // Scan request data for denied keywords
      Utils.checkProhibitedKeywords(config, metadata);
      Utils.checkProhibitedKeywords(config, tags);
    } catch (error) {
      next(new Parse.Error(Parse.Error.INVALID_KEY_NAME, error));
      return;
    }
    file.setTags(tags);
    file.setMetadata(metadata);
    const fileSize = Buffer.byteLength(req.body);
    const fileObject = { file, fileSize };
    try {
      // run beforeSaveFile trigger
      const triggerResult = await triggers.maybeRunFileTrigger(
        triggers.Types.beforeSave,
        fileObject,
        config,
        req.auth
      );
      let saveResult;
      // if a new ParseFile is returned check if it's an already saved file
      if (triggerResult instanceof Parse.File) {
        fileObject.file = triggerResult;
        if (triggerResult.url()) {
          // set fileSize to null because we wont know how big it is here
          fileObject.fileSize = null;
          saveResult = {
            url: triggerResult.url(),
            name: triggerResult._name,
          };
        }
      }
      // if the file returned by the trigger has already been saved skip saving anything
      if (!saveResult) {
        // if the ParseFile returned is type uri, download the file before saving it
        await addFileDataIfNeeded(fileObject.file);
        // update fileSize
        const bufferData = Buffer.from(fileObject.file._data, 'base64');
        fileObject.fileSize = Buffer.byteLength(bufferData);
        // prepare file options
        const fileOptions = {
          metadata: fileObject.file._metadata,
        };
        // some s3-compatible providers (DigitalOcean, Linode) do not accept tags
        // so we do not include the tags option if it is empty.
        const fileTags =
          Object.keys(fileObject.file._tags).length > 0 ? { tags: fileObject.file._tags } : {};
        Object.assign(fileOptions, fileTags);
        // save file
        const createFileResult = await filesController.createFile(
          config,
          fileObject.file._name,
          bufferData,
          fileObject.file._source.type,
          fileOptions
        );
        // update file with new data
        fileObject.file._name = createFileResult.name;
        fileObject.file._url = createFileResult.url;
        fileObject.file._requestTask = null;
        fileObject.file._previousSave = Promise.resolve(fileObject.file);
        saveResult = {
          url: createFileResult.url,
          name: createFileResult.name,
        };
      }
      // run afterSaveFile trigger
      await triggers.maybeRunFileTrigger(triggers.Types.afterSave, fileObject, config, req.auth);
      res.status(201);
      res.set('Location', saveResult.url);
      res.json(saveResult);
    } catch (e) {
      logger.error('Error creating a file: ', e);
      const error = triggers.resolveError(e, {
        code: Parse.Error.FILE_SAVE_ERROR,
        message: `Could not store file: ${fileObject.file._name}.`,
      });
      next(error);
    }
  }

--- a/src/Routers/FilesRouter.js
+++ b/src/Routers/FilesRouter.js
@@ -56,9 +56,9 @@
       } else if (contentType && contentType.includes('/')) {
         extension = contentType.split('/')[1];
       }
-      extension = extension.split(' ').join('');
+      extension = extension?.split(' ')?.join('');

-      if (!isValidExtension(extension)) {
+      if (extension && !isValidExtension(extension)) {
         next(
           new Parse.Error(
             Parse.Error.FILE_SAVE_ERROR,


math.js before 3.17.0 had an issue where private properties such as a constructor could be replaced by using unicode characters when creating an object.

Improper Input Validation

The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.

  function compileObjectNode(node, defs, args) {
    if (!(node instanceof ObjectNode)) {
      throw new TypeError('No valid ObjectNode')
    }

    var entries = [];
    for (var key in node.properties) {
      if (hasOwnProperty(node.properties, key)) {
        if (!isSafeProperty(node.properties, key)) {
          throw new Error('No access to property "' + key + '"');
        }

        entries.push(stringify(key) + ': ' + compile(node.properties[key], defs, args));
      }
    }
    return '{' + entries.join(', ') + '}';
  }

  function compileObjectNode(node, defs, args) {
    if (!(node instanceof ObjectNode)) {
      throw new TypeError('No valid ObjectNode')
    }

    var entries = [];
    for (var key in node.properties) {
      if (hasOwnProperty(node.properties, key)) {
        // we stringify/parse the key here to resolve unicode characters,
        // so you cannot create a key like {"co\\u006Estructor": null}
        var stringifiedKey = stringify(key)
        var parsedKey = JSON.parse(stringifiedKey)
        if (!isSafeProperty(node.properties, parsedKey)) {
          throw new Error('No access to property "' + parsedKey + '"');
        }

        entries.push(stringifiedKey + ': ' + compile(node.properties[key], defs, args));
      }
    }
    return '{' + entries.join(', ') + '}';
  }

--- a/lib/expression/node/ObjectNode.js
+++ b/lib/expression/node/ObjectNode.js
@@ -6,11 +6,15 @@
     var entries = [];
     for (var key in node.properties) {
       if (hasOwnProperty(node.properties, key)) {
-        if (!isSafeProperty(node.properties, key)) {
-          throw new Error('No access to property "' + key + '"');
+        // we stringify/parse the key here to resolve unicode characters,
+        // so you cannot create a key like {"co\\u006Estructor": null}
+        var stringifiedKey = stringify(key)
+        var parsedKey = JSON.parse(stringifiedKey)
+        if (!isSafeProperty(node.properties, parsedKey)) {
+          throw new Error('No access to property "' + parsedKey + '"');
         }

-        entries.push(stringify(key) + ': ' + compile(node.properties[key], defs, args));
+        entries.push(stringifiedKey + ': ' + compile(node.properties[key], defs, args));
       }
     }
     return '{' + entries.join(', ') + '}';

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

cd /workspace/mathjs
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --grep "should not allow disguising forbidden properties with unicode characters" test/expression/security.test.js

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

cd /workspace/mathjs
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha --timeout 3000 test/expression/security.test.js


In Go Ethereum (aka geth) before 1.8.14, TraceChain in eth/api_tracer.go does not verify that the end block is after the start block.

func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
	// Fetch the block interval that we want to trace
	var from, to *types.Block

	switch start {
	case rpc.PendingBlockNumber:
		from = api.eth.miner.PendingBlock()
	case rpc.LatestBlockNumber:
		from = api.eth.blockchain.CurrentBlock()
	default:
		from = api.eth.blockchain.GetBlockByNumber(uint64(start))
	}
	switch end {
	case rpc.PendingBlockNumber:
		to = api.eth.miner.PendingBlock()
	case rpc.LatestBlockNumber:
		to = api.eth.blockchain.CurrentBlock()
	default:
		to = api.eth.blockchain.GetBlockByNumber(uint64(end))
	}
	// Trace the chain if we've found all our blocks
	if from == nil {
		return nil, fmt.Errorf("starting block #%d not found", start)
	}
	if to == nil {
		return nil, fmt.Errorf("end block #%d not found", end)
	}
	return api.traceChain(ctx, from, to, config)
}

func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
	// Fetch the block interval that we want to trace
	var from, to *types.Block

	switch start {
	case rpc.PendingBlockNumber:
		from = api.eth.miner.PendingBlock()
	case rpc.LatestBlockNumber:
		from = api.eth.blockchain.CurrentBlock()
	default:
		from = api.eth.blockchain.GetBlockByNumber(uint64(start))
	}
	switch end {
	case rpc.PendingBlockNumber:
		to = api.eth.miner.PendingBlock()
	case rpc.LatestBlockNumber:
		to = api.eth.blockchain.CurrentBlock()
	default:
		to = api.eth.blockchain.GetBlockByNumber(uint64(end))
	}
	// Trace the chain if we've found all our blocks
	if from == nil {
		return nil, fmt.Errorf("starting block #%d not found", start)
	}
	if to == nil {
		return nil, fmt.Errorf("end block #%d not found", end)
	}
	if from.Number().Cmp(to.Number()) >= 0 {
		return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start)
	}
	return api.traceChain(ctx, from, to, config)
}

--- a/eth/api_tracer.go
+++ b/eth/api_tracer.go
@@ -27,3 +27,6 @@
 	}
 	return api.traceChain(ctx, from, to, config)
 }
+
+// traceChain configures a new tracer according to the provided configuration, and
+// executes all the transactions contained within. The return value will be one item


An incorrect implementation of "XEP-0280: Message Carbons" in multiple XMPP clients allows a remote attacker to impersonate any user, including contacts, in the vulnerable application's display. This allows for various kinds of social engineering attacks. This CVE is for SleekXMPP up to 1.3.1 and Slixmpp all versions up to 1.2.3, as bundled in poezio (0.8 - 0.10) and other products.

    def _handle_carbon_received(self, msg):
        self.xmpp.event('carbon_received', msg)

    def _handle_carbon_sent(self, msg):
        self.xmpp.event('carbon_sent', msg)

    def _handle_carbon_received(self, msg):
        if msg['from'].bare == self.xmpp.boundjid.bare:
            self.xmpp.event('carbon_received', msg)

    def _handle_carbon_sent(self, msg):
        if msg['from'].bare == self.xmpp.boundjid.bare:
            self.xmpp.event('carbon_sent', msg)

--- a/slixmpp/plugins/xep_0280/carbons.py
+++ b/slixmpp/plugins/xep_0280/carbons.py
@@ -1,2 +1,3 @@
     def _handle_carbon_received(self, msg):
-        self.xmpp.event('carbon_received', msg)
+        if msg['from'].bare == self.xmpp.boundjid.bare:
+            self.xmpp.event('carbon_received', msg)

--- a/slixmpp/plugins/xep_0280/carbons.py
+++ b/slixmpp/plugins/xep_0280/carbons.py
@@ -1,2 +1,3 @@
     def _handle_carbon_sent(self, msg):
-        self.xmpp.event('carbon_sent', msg)
+        if msg['from'].bare == self.xmpp.boundjid.bare:
+            self.xmpp.event('carbon_sent', msg)


Sentry is an error tracking and performance monitoring platform. Starting in version 22.1.0 and prior to version 23.7.2, an attacker with access to a token with few or no scopes can query `/api/0/api-tokens/` for a list of all tokens created by a user, including tokens with greater scopes, and use those tokens in other requests. There is no evidence that the issue was exploited on `sentry.io`. For self-hosted users, it is advised to rotate user auth tokens. A fix is available in version 23.7.2 of `sentry` and `self-hosted`. There are no known workarounds.

Improper Authentication

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

class ApiTokensEndpoint(Endpoint):
    authentication_classes = (SessionAuthentication,)
    permission_classes = (IsAuthenticated,)

    @method_decorator(never_cache)
    def get(self, request: Request) -> Response:
        user_id = request.user.id
        if is_active_superuser(request):
            user_id = request.GET.get("userId", user_id)

        token_list = list(
            ApiToken.objects.filter(application__isnull=True, user_id=user_id).select_related(
                "application"
            )
        )

        return Response(serialize(token_list, request.user))

    @method_decorator(never_cache)
    def post(self, request: Request) -> Response:
        serializer = ApiTokenSerializer(data=request.data)

        if serializer.is_valid():
            result = serializer.validated_data

            token = ApiToken.objects.create(
                user_id=request.user.id,
                scope_list=result["scopes"],
                refresh_token=None,
                expires_at=None,
            )

            capture_security_activity(
                account=request.user,
                type="api-token-generated",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={},
                send_email=True,
            )

            analytics.record("api_token.created", user_id=request.user.id)

            return Response(serialize(token, request.user), status=201)
        return Response(serializer.errors, status=400)

    @method_decorator(never_cache)
    def delete(self, request: Request):
        user_id = request.user.id
        if is_active_superuser(request):
            user_id = request.data.get("userId", user_id)
        token = request.data.get("token")
        if not token:
            return Response({"token": ""}, status=400)

        ApiToken.objects.filter(user_id=user_id, token=token, application__isnull=True).delete()

        analytics.record("api_token.deleted", user_id=request.user.id)

        return Response(status=204)

class SessionNoAuthTokenAuthentication(SessionAuthentication):
    def authenticate(self, request: Request):
        auth = get_authorization_header(request)
        if auth:
            return None
        return super().authenticate(request)

class ApiTokensEndpoint(Endpoint):
    authentication_classes = (SessionNoAuthTokenAuthentication,)
    permission_classes = (IsAuthenticated,)

    @method_decorator(never_cache)
    def get(self, request: Request) -> Response:
        user_id = request.user.id
        if is_active_superuser(request):
            user_id = request.GET.get("userId", user_id)

        token_list = list(
            ApiToken.objects.filter(application__isnull=True, user_id=user_id).select_related(
                "application"
            )
        )

        return Response(serialize(token_list, request.user))

    @method_decorator(never_cache)
    def post(self, request: Request) -> Response:
        serializer = ApiTokenSerializer(data=request.data)

        if serializer.is_valid():
            result = serializer.validated_data

            token = ApiToken.objects.create(
                user_id=request.user.id,
                scope_list=result["scopes"],
                refresh_token=None,
                expires_at=None,
            )

            capture_security_activity(
                account=request.user,
                type="api-token-generated",
                actor=request.user,
                ip_address=request.META["REMOTE_ADDR"],
                context={},
                send_email=True,
            )

            analytics.record("api_token.created", user_id=request.user.id)

            return Response(serialize(token, request.user), status=201)
        return Response(serializer.errors, status=400)

    @method_decorator(never_cache)
    def delete(self, request: Request):
        user_id = request.user.id
        if is_active_superuser(request):
            user_id = request.data.get("userId", user_id)
        token = request.data.get("token")
        if not token:
            return Response({"token": ""}, status=400)

        ApiToken.objects.filter(user_id=user_id, token=token, application__isnull=True).delete()

        analytics.record("api_token.deleted", user_id=request.user.id)

        return Response(status=204)

--- a/src/sentry/api/endpoints/api_tokens.py
+++ b/src/sentry/api/authentication.py
@@ -1,60 +1,6 @@
-class ApiTokensEndpoint(Endpoint):
-    authentication_classes = (SessionAuthentication,)
-    permission_classes = (IsAuthenticated,)
-
-    @method_decorator(never_cache)
-    def get(self, request: Request) -> Response:
-        user_id = request.user.id
-        if is_active_superuser(request):
-            user_id = request.GET.get("userId", user_id)
-
-        token_list = list(
-            ApiToken.objects.filter(application__isnull=True, user_id=user_id).select_related(
-                "application"
-            )
-        )
-
-        return Response(serialize(token_list, request.user))
-
-    @method_decorator(never_cache)
-    def post(self, request: Request) -> Response:
-        serializer = ApiTokenSerializer(data=request.data)
-
-        if serializer.is_valid():
-            result = serializer.validated_data
-
-            token = ApiToken.objects.create(
-                user_id=request.user.id,
-                scope_list=result["scopes"],
-                refresh_token=None,
-                expires_at=None,
-            )
-
-            capture_security_activity(
-                account=request.user,
-                type="api-token-generated",
-                actor=request.user,
-                ip_address=request.META["REMOTE_ADDR"],
-                context={},
-                send_email=True,
-            )
-
-            analytics.record("api_token.created", user_id=request.user.id)
-
-            return Response(serialize(token, request.user), status=201)
-        return Response(serializer.errors, status=400)
-
-    @method_decorator(never_cache)
-    def delete(self, request: Request):
-        user_id = request.user.id
-        if is_active_superuser(request):
-            user_id = request.data.get("userId", user_id)
-        token = request.data.get("token")
-        if not token:
-            return Response({"token": ""}, status=400)
-
-        ApiToken.objects.filter(user_id=user_id, token=token, application__isnull=True).delete()
-
-        analytics.record("api_token.deleted", user_id=request.user.id)
-
-        return Response(status=204)
+class SessionNoAuthTokenAuthentication(SessionAuthentication):
+    def authenticate(self, request: Request):
+        auth = get_authorization_header(request)
+        if auth:
+            return None
+        return super().authenticate(request)

--- /dev/null
+++ b/src/sentry/api/authentication.py
@@ -0,0 +1,60 @@
+class ApiTokensEndpoint(Endpoint):
+    authentication_classes = (SessionNoAuthTokenAuthentication,)
+    permission_classes = (IsAuthenticated,)
+
+    @method_decorator(never_cache)
+    def get(self, request: Request) -> Response:
+        user_id = request.user.id
+        if is_active_superuser(request):
+            user_id = request.GET.get("userId", user_id)
+
+        token_list = list(
+            ApiToken.objects.filter(application__isnull=True, user_id=user_id).select_related(
+                "application"
+            )
+        )
+
+        return Response(serialize(token_list, request.user))
+
+    @method_decorator(never_cache)
+    def post(self, request: Request) -> Response:
+        serializer = ApiTokenSerializer(data=request.data)
+
+        if serializer.is_valid():
+            result = serializer.validated_data
+
+            token = ApiToken.objects.create(
+                user_id=request.user.id,
+                scope_list=result["scopes"],
+                refresh_token=None,
+                expires_at=None,
+            )
+
+            capture_security_activity(
+                account=request.user,
+                type="api-token-generated",
+                actor=request.user,
+                ip_address=request.META["REMOTE_ADDR"],
+                context={},
+                send_email=True,
+            )
+
+            analytics.record("api_token.created", user_id=request.user.id)
+
+            return Response(serialize(token, request.user), status=201)
+        return Response(serializer.errors, status=400)
+
+    @method_decorator(never_cache)
+    def delete(self, request: Request):
+        user_id = request.user.id
+        if is_active_superuser(request):
+            user_id = request.data.get("userId", user_id)
+        token = request.data.get("token")
+        if not token:
+            return Response({"token": ""}, status=400)
+
+        ApiToken.objects.filter(user_id=user_id, token=token, application__isnull=True).delete()
+
+        analytics.record("api_token.deleted", user_id=request.user.id)
+
+        return Response(status=204)


OAuth2-Proxy is an open source reverse proxy that provides authentication with Google, Github or other providers. The `--gitlab-group` flag for group-based authorization in the GitLab provider stopped working in the v7.0.0 release. Regardless of the flag settings, authorization wasn't restricted. Additionally, any authenticated users had whichever groups were set in `--gitlab-group` added to the new `X-Forwarded-Groups` header to the upstream application. While adding GitLab project based authorization support in #630, a bug was introduced where the user session's groups field was populated with the `--gitlab-group` config entries instead of pulling the individual user's group membership from the GitLab Userinfo endpoint. When the session groups where compared against the allowed groups for authorization, they matched improperly (since both lists were populated with the same data) so authorization was allowed. This impacts GitLab Provider users who relies on group membership for authorization restrictions. Any authenticated users in your GitLab environment can access your applications regardless of `--gitlab-group` membership restrictions. This is patched in v7.1.0. There is no workaround for the Group membership bug. But `--gitlab-project` can be set to use Project membership as the authorization checks instead of groups; it is not broken.

func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
	// Retrieve user info
	userInfo, err := p.getUserInfo(ctx, s)
	if err != nil {
		return fmt.Errorf("failed to retrieve user info: %v", err)
	}

	// Check if email is verified
	if !p.AllowUnverifiedEmail && !userInfo.EmailVerified {
		return fmt.Errorf("user email is not verified")
	}

	s.User = userInfo.Username
	s.Email = userInfo.Email

	p.addGroupsToSession(ctx, s)

	p.addProjectsToSession(ctx, s)

	return nil

}

// addGroupsToSession projects into session.Groups
func (p *GitLabProvider) addGroupsToSession(ctx context.Context, s *sessions.SessionState) {
	// Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
	for _, group := range p.Groups {
		s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group))
	}
}

func (p *GitLabProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
	// Retrieve user info
	userInfo, err := p.getUserInfo(ctx, s)
	if err != nil {
		return fmt.Errorf("failed to retrieve user info: %v", err)
	}

	// Check if email is verified
	if !p.AllowUnverifiedEmail && !userInfo.EmailVerified {
		return fmt.Errorf("user email is not verified")
	}

	s.User = userInfo.Username
	s.Email = userInfo.Email
	for _, group := range userInfo.Groups {
		s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group))
	}

	p.addProjectsToSession(ctx, s)

	return nil
}

--- a/./providers/gitlab.go
+++ b/./providers/gitlab.go
@@ -12,19 +12,11 @@

 	s.User = userInfo.Username
 	s.Email = userInfo.Email
-
-	p.addGroupsToSession(ctx, s)
+	for _, group := range userInfo.Groups {
+		s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group))
+	}

 	p.addProjectsToSession(ctx, s)

 	return nil
-
 }
-
-// addGroupsToSession projects into session.Groups
-func (p *GitLabProvider) addGroupsToSession(ctx context.Context, s *sessions.SessionState) {
-	// Iterate over projects, check if oauth2-proxy can get project information on behalf of the user
-	for _, group := range p.Groups {
-		s.Groups = append(s.Groups, fmt.Sprintf("group:%s", group))
-	}
-}

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

cd /workspace/oauth2-proxy
rm -rf ./providers/gitlab_test.go
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -v -run 'TestProviderSuite/when_filtering_on_gitlab_entities' github.com/oauth2-proxy/oauth2-proxy/v7/providers