Talos Linux is a Linux distribution built for Kubernetes deployments. Talos worker nodes use a join token to get accepted into the Talos cluster. Due to improper validation of the request while signing a worker node CSR (certificate signing request) Talos control plane node might issue Talos API certificate which allows full access to Talos API on a control plane node. Accessing Talos API with full level access on a control plane node might reveal sensitive information which allows full level access to the cluster (Kubernetes and Talos PKI, etc.). Talos API join token is stored in the machine configuration on the worker node. When configured correctly, Kubernetes workloads don't have access to the machine configuration, but due to a misconfiguration workload might access the machine configuration and reveal the join token. This problem has been fixed in Talos 1.2.2. Enabling the Pod Security Standards mitigates the vulnerability by denying hostPath mounts and host networking by default in the baseline policy. Clusters that don't run untrusted workloads are not affected. Clusters with correct Pod Security configurations which don't allow hostPath mounts, and secure access to cloud metadata server (or machine configuration is not supplied via cloud metadata server) are not affected.

func (r *Registrator) Certificate(ctx context.Context, in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
	osRoot, err := safe.StateGet[*secrets.OSRoot](ctx, r.Resources, resource.NewMetadata(secrets.NamespaceName, secrets.OSRootType, secrets.OSRootID, resource.VersionUndefined))
	if err != nil {
		return nil, err
	}

	// TODO: Verify that the request is coming from the IP addresss declared in
	// the CSR.
	signed, err := x509.NewCertificateFromCSRBytes(osRoot.TypedSpec().CA.Crt, osRoot.TypedSpec().CA.Key, in.Csr)
	if err != nil {
		return
	}

	resp = &securityapi.CertificateResponse{
		Ca:  osRoot.TypedSpec().CA.Crt,
		Crt: signed.X509CertificatePEM,
	}

	return resp, nil
}

import (
	"context"
	stdx509 "crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"log"

	"github.com/cosi-project/runtime/pkg/resource"
	"github.com/cosi-project/runtime/pkg/safe"
	"github.com/cosi-project/runtime/pkg/state"
	"github.com/siderolabs/crypto/x509"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/status"

	securityapi "github.com/talos-systems/talos/pkg/machinery/api/security"
	"github.com/talos-systems/talos/pkg/machinery/resources/secrets"
)

func (r *Registrator) Certificate(ctx context.Context, in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
	remotePeer, ok := peer.FromContext(ctx)
	if !ok {
		return nil, status.Error(codes.PermissionDenied, "peer not found")
	}

	osRoot, err := safe.StateGet[*secrets.OSRoot](ctx, r.Resources, resource.NewMetadata(secrets.NamespaceName, secrets.OSRootType, secrets.OSRootID, resource.VersionUndefined))
	if err != nil {
		return nil, err
	}

	// decode and validate CSR
	csrPemBlock, _ := pem.Decode(in.Csr)
	if csrPemBlock == nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to decode CSR")
	}

	request, err := stdx509.ParseCertificateRequest(csrPemBlock.Bytes)
	if err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "failed to parse CSR: %s", err)
	}

	log.Printf("received CSR signing request from %s: subject %s dns names %s addresses %s", remotePeer.Addr, request.Subject, request.DNSNames, request.IPAddresses)

	// allow only server auth certificates
	x509Opts := []x509.Option{
		x509.KeyUsage(stdx509.KeyUsageDigitalSignature),
		x509.ExtKeyUsage([]stdx509.ExtKeyUsage{stdx509.ExtKeyUsageServerAuth}),
	}

	// don't allow any certificates which can be used for client authentication
	//
	// we don't return an error here, as otherwise workers running old versions of Talos
	// will fail to provision client certificate and will never launch apid
	//
	// instead, the returned certificate will be rejected when being used
	if len(request.Subject.Organization) > 0 {
		log.Printf("removing client auth organization from CSR: %s", request.Subject.Organization)

		x509Opts = append(x509Opts, x509.OverrideSubject(func(subject *pkix.Name) {
			subject.Organization = nil
		}))
	}

	// TODO: Verify that the request is coming from the IP address declared in
	// the CSR.
	signed, err := x509.NewCertificateFromCSRBytes(
		osRoot.TypedSpec().CA.Crt,
		osRoot.TypedSpec().CA.Key,
		in.Csr,
		x509Opts...,
	)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to sign CSR: %s", err)
	}

	resp = &securityapi.CertificateResponse{
		Ca:  osRoot.TypedSpec().CA.Crt,
		Crt: signed.X509CertificatePEM,
	}

	return resp, nil
}

--- a/internal/app/trustd/internal/reg/reg.go
+++ b/internal/app/trustd/internal/reg/reg.go
@@ -1,14 +1,57 @@
 func (r *Registrator) Certificate(ctx context.Context, in *securityapi.CertificateRequest) (resp *securityapi.CertificateResponse, err error) {
+	remotePeer, ok := peer.FromContext(ctx)
+	if !ok {
+		return nil, status.Error(codes.PermissionDenied, "peer not found")
+	}
+
 	osRoot, err := safe.StateGet[*secrets.OSRoot](ctx, r.Resources, resource.NewMetadata(secrets.NamespaceName, secrets.OSRootType, secrets.OSRootID, resource.VersionUndefined))
 	if err != nil {
 		return nil, err
 	}

-	// TODO: Verify that the request is coming from the IP addresss declared in
+	// decode and validate CSR
+	csrPemBlock, _ := pem.Decode(in.Csr)
+	if csrPemBlock == nil {
+		return nil, status.Errorf(codes.InvalidArgument, "failed to decode CSR")
+	}
+
+	request, err := stdx509.ParseCertificateRequest(csrPemBlock.Bytes)
+	if err != nil {
+		return nil, status.Errorf(codes.InvalidArgument, "failed to parse CSR: %s", err)
+	}
+
+	log.Printf("received CSR signing request from %s: subject %s dns names %s addresses %s", remotePeer.Addr, request.Subject, request.DNSNames, request.IPAddresses)
+
+	// allow only server auth certificates
+	x509Opts := []x509.Option{
+		x509.KeyUsage(stdx509.KeyUsageDigitalSignature),
+		x509.ExtKeyUsage([]stdx509.ExtKeyUsage{stdx509.ExtKeyUsageServerAuth}),
+	}
+
+	// don't allow any certificates which can be used for client authentication
+	//
+	// we don't return an error here, as otherwise workers running old versions of Talos
+	// will fail to provision client certificate and will never launch apid
+	//
+	// instead, the returned certificate will be rejected when being used
+	if len(request.Subject.Organization) > 0 {
+		log.Printf("removing client auth organization from CSR: %s", request.Subject.Organization)
+
+		x509Opts = append(x509Opts, x509.OverrideSubject(func(subject *pkix.Name) {
+			subject.Organization = nil
+		}))
+	}
+
+	// TODO: Verify that the request is coming from the IP address declared in
 	// the CSR.
-	signed, err := x509.NewCertificateFromCSRBytes(osRoot.TypedSpec().CA.Crt, osRoot.TypedSpec().CA.Key, in.Csr)
+	signed, err := x509.NewCertificateFromCSRBytes(
+		osRoot.TypedSpec().CA.Crt,
+		osRoot.TypedSpec().CA.Key,
+		in.Csr,
+		x509Opts...,
+	)
 	if err != nil {
-		return
+		return nil, status.Errorf(codes.Internal, "failed to sign CSR: %s", err)
 	}

 	resp = &securityapi.CertificateResponse{

--- /dev/null
+++ b/internal/app/trustd/internal/reg/reg.go
@@ -0,0 +1,19 @@
+import (
+	"context"
+	stdx509 "crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/pem"
+	"log"
+
+	"github.com/cosi-project/runtime/pkg/resource"
+	"github.com/cosi-project/runtime/pkg/safe"
+	"github.com/cosi-project/runtime/pkg/state"
+	"github.com/siderolabs/crypto/x509"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/peer"
+	"google.golang.org/grpc/status"
+
+	securityapi "github.com/talos-systems/talos/pkg/machinery/api/security"
+	"github.com/talos-systems/talos/pkg/machinery/resources/secrets"
+)

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

cd /workspace/talos
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestCertificate$ github.com/talos-systems/talos/internal/app/trustd/internal/reg

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

cd /workspace/talos
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestEmpty)$' github.com/talos-systems/talos/internal/app/trustd/internal/reg


Prometheus is an open-source monitoring system and time series database. In 2.23.0, Prometheus changed its default UI to the New ui. To ensure a seamless transition, the URL's prefixed by /new redirect to /. Due to a bug in the code, it is possible for an attacker to craft an URL that can redirect to any other URL, in the /new endpoint. If a user visits a prometheus server with a specially crafted address, they can be redirected to an arbitrary URL. The issue was patched in the 2.26.1 and 2.27.1 releases. In 2.28.0, the /new endpoint will be removed completely. The workaround is to disable access to /new via a reverse proxy in front of Prometheus.

URL Redirection to Untrusted Site ('Open Redirect')

The web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect.

func New(logger log.Logger, o *Options) *Handler {
	if logger == nil {
		logger = log.NewNopLogger()
	}

	m := newMetrics(o.Registerer)
	router := route.New().
		WithInstrumentation(m.instrumentHandler).
		WithInstrumentation(setPathWithPrefix(""))

	cwd, err := os.Getwd()
	if err != nil {
		cwd = "<error retrieving current working directory>"
	}

	h := &Handler{
		logger: logger,

		gatherer: o.Gatherer,
		metrics:  m,

		router:      router,
		quitCh:      make(chan struct{}),
		reloadCh:    make(chan chan error),
		options:     o,
		versionInfo: o.Version,
		birth:       time.Now().UTC(),
		cwd:         cwd,
		flagsMap:    o.Flags,

		context:         o.Context,
		scrapeManager:   o.ScrapeManager,
		ruleManager:     o.RuleManager,
		queryEngine:     o.QueryEngine,
		lookbackDelta:   o.LookbackDelta,
		storage:         o.Storage,
		localStorage:    o.LocalStorage,
		exemplarStorage: o.ExemplarStorage,
		notifier:        o.Notifier,

		now: model.Now,
	}
	h.ready.Store(0)

	factoryTr := func(_ context.Context) api_v1.TargetRetriever { return h.scrapeManager }
	factoryAr := func(_ context.Context) api_v1.AlertmanagerRetriever { return h.notifier }
	FactoryRr := func(_ context.Context) api_v1.RulesRetriever { return h.ruleManager }

	var app storage.Appendable
	if o.RemoteWriteReceiver {
		app = h.storage
	}

	h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, app, h.exemplarStorage, factoryTr, factoryAr,
		func() config.Config {
			h.mtx.RLock()
			defer h.mtx.RUnlock()
			return *h.config
		},
		o.Flags,
		api_v1.GlobalURLOptions{
			ListenAddress: o.ListenAddress,
			Host:          o.ExternalURL.Host,
			Scheme:        o.ExternalURL.Scheme,
		},
		h.testReady,
		h.options.LocalStorage,
		h.options.TSDBDir,
		h.options.EnableAdminAPI,
		logger,
		FactoryRr,
		h.options.RemoteReadSampleLimit,
		h.options.RemoteReadConcurrencyLimit,
		h.options.RemoteReadBytesInFrame,
		h.options.CORSOrigin,
		h.runtimeInfo,
		h.versionInfo,
		o.Gatherer,
		o.Registerer,
	)

	if o.RoutePrefix != "/" {
		// If the prefix is missing for the root path, prepend it.
		router.Get("/", func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, o.RoutePrefix, http.StatusFound)
		})
		router = router.WithPrefix(o.RoutePrefix)
	}

	readyf := h.testReady

	router.Get("/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
	})
	router.Get("/classic/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/classic/graph"), http.StatusFound)
	})

	// Redirect the original React UI's path (under "/new") to its new path at the root.
	router.Get("/new/*path", func(w http.ResponseWriter, r *http.Request) {
		p := route.Param(r.Context(), "path")
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, strings.TrimPrefix(p, "/new"))+"?"+r.URL.RawQuery, http.StatusFound)
	})

	router.Get("/classic/alerts", readyf(h.alerts))
	router.Get("/classic/graph", readyf(h.graph))
	router.Get("/classic/status", readyf(h.status))
	router.Get("/classic/flags", readyf(h.flags))
	router.Get("/classic/config", readyf(h.serveConfig))
	router.Get("/classic/rules", readyf(h.rules))
	router.Get("/classic/targets", readyf(h.targets))
	router.Get("/classic/service-discovery", readyf(h.serviceDiscovery))
	router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
		fs := server.StaticFileServer(ui.Assets)
		fs.ServeHTTP(w, r)
	})
	// Make sure that "<path-prefix>/classic" is redirected to "<path-prefix>/classic/" and
	// not just the naked "/classic/", which would be the default behavior of the router
	// with the "RedirectTrailingSlash" option (https://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash),
	// and which breaks users with a --web.route-prefix that deviates from the path derived
	// from the external URL.
	// See https://github.com/prometheus/prometheus/issues/6163#issuecomment-553855129.
	router.Get("/classic", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, "classic")+"/", http.StatusFound)
	})

	router.Get("/version", h.version)
	router.Get("/metrics", promhttp.Handler().ServeHTTP)

	router.Get("/federate", readyf(httputil.CompressionHandler{
		Handler: http.HandlerFunc(h.federation),
	}.ServeHTTP))

	router.Get("/consoles/*filepath", readyf(h.consoles))

	serveReactApp := func(w http.ResponseWriter, r *http.Request) {
		f, err := ui.Assets.Open("/static/react/index.html")
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error opening React index.html: %v", err)
			return
		}
		idx, err := ioutil.ReadAll(f)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error reading React index.html: %v", err)
			return
		}
		replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
		replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
		w.Write(replacedIdx)
	}

	// Serve the React app.
	for _, p := range reactRouterPaths {
		router.Get(p, serveReactApp)
	}

	// The favicon and manifest are bundled as part of the React app, but we want to serve
	// them on the root.
	for _, p := range []string{"/favicon.ico", "/manifest.json"} {
		assetPath := "/static/react" + p
		router.Get(p, func(w http.ResponseWriter, r *http.Request) {
			r.URL.Path = assetPath
			fs := server.StaticFileServer(ui.Assets)
			fs.ServeHTTP(w, r)
		})
	}

	// Static files required by the React app.
	router.Get("/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = path.Join("/static/react/static", route.Param(r.Context(), "filepath"))
		fs := server.StaticFileServer(ui.Assets)
		fs.ServeHTTP(w, r)
	})

	if o.UserAssetsPath != "" {
		router.Get("/user/*filepath", route.FileServe(o.UserAssetsPath))
	}

	if o.EnableLifecycle {
		router.Post("/-/quit", h.quit)
		router.Put("/-/quit", h.quit)
		router.Post("/-/reload", h.reload)
		router.Put("/-/reload", h.reload)
	} else {
		forbiddenAPINotEnabled := func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte("Lifecycle API is not enabled."))
		}
		router.Post("/-/quit", forbiddenAPINotEnabled)
		router.Put("/-/quit", forbiddenAPINotEnabled)
		router.Post("/-/reload", forbiddenAPINotEnabled)
		router.Put("/-/reload", forbiddenAPINotEnabled)
	}
	router.Get("/-/quit", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Only POST or PUT requests allowed"))
	})
	router.Get("/-/reload", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Only POST or PUT requests allowed"))
	})

	router.Get("/debug/*subpath", serveDebug)
	router.Post("/debug/*subpath", serveDebug)

	router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, "Prometheus is Healthy.\n")
	})
	router.Get("/-/ready", readyf(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, "Prometheus is Ready.\n")
	}))

	return h
}

func New(logger log.Logger, o *Options) *Handler {
	if logger == nil {
		logger = log.NewNopLogger()
	}

	m := newMetrics(o.Registerer)
	router := route.New().
		WithInstrumentation(m.instrumentHandler).
		WithInstrumentation(setPathWithPrefix(""))

	cwd, err := os.Getwd()
	if err != nil {
		cwd = "<error retrieving current working directory>"
	}

	h := &Handler{
		logger: logger,

		gatherer: o.Gatherer,
		metrics:  m,

		router:      router,
		quitCh:      make(chan struct{}),
		reloadCh:    make(chan chan error),
		options:     o,
		versionInfo: o.Version,
		birth:       time.Now().UTC(),
		cwd:         cwd,
		flagsMap:    o.Flags,

		context:         o.Context,
		scrapeManager:   o.ScrapeManager,
		ruleManager:     o.RuleManager,
		queryEngine:     o.QueryEngine,
		lookbackDelta:   o.LookbackDelta,
		storage:         o.Storage,
		localStorage:    o.LocalStorage,
		exemplarStorage: o.ExemplarStorage,
		notifier:        o.Notifier,

		now: model.Now,
	}
	h.ready.Store(0)

	factoryTr := func(_ context.Context) api_v1.TargetRetriever { return h.scrapeManager }
	factoryAr := func(_ context.Context) api_v1.AlertmanagerRetriever { return h.notifier }
	FactoryRr := func(_ context.Context) api_v1.RulesRetriever { return h.ruleManager }

	var app storage.Appendable
	if o.RemoteWriteReceiver {
		app = h.storage
	}

	h.apiV1 = api_v1.NewAPI(h.queryEngine, h.storage, app, h.exemplarStorage, factoryTr, factoryAr,
		func() config.Config {
			h.mtx.RLock()
			defer h.mtx.RUnlock()
			return *h.config
		},
		o.Flags,
		api_v1.GlobalURLOptions{
			ListenAddress: o.ListenAddress,
			Host:          o.ExternalURL.Host,
			Scheme:        o.ExternalURL.Scheme,
		},
		h.testReady,
		h.options.LocalStorage,
		h.options.TSDBDir,
		h.options.EnableAdminAPI,
		logger,
		FactoryRr,
		h.options.RemoteReadSampleLimit,
		h.options.RemoteReadConcurrencyLimit,
		h.options.RemoteReadBytesInFrame,
		h.options.CORSOrigin,
		h.runtimeInfo,
		h.versionInfo,
		o.Gatherer,
		o.Registerer,
	)

	if o.RoutePrefix != "/" {
		// If the prefix is missing for the root path, prepend it.
		router.Get("/", func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, o.RoutePrefix, http.StatusFound)
		})
		router = router.WithPrefix(o.RoutePrefix)
	}

	readyf := h.testReady

	router.Get("/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/graph"), http.StatusFound)
	})
	router.Get("/classic/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, "/classic/graph"), http.StatusFound)
	})

	// Redirect the original React UI's path (under "/new") to its new path at the root.
	router.Get("/new/*path", func(w http.ResponseWriter, r *http.Request) {
		p := route.Param(r.Context(), "path")
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, p)+"?"+r.URL.RawQuery, http.StatusFound)
	})

	router.Get("/classic/alerts", readyf(h.alerts))
	router.Get("/classic/graph", readyf(h.graph))
	router.Get("/classic/status", readyf(h.status))
	router.Get("/classic/flags", readyf(h.flags))
	router.Get("/classic/config", readyf(h.serveConfig))
	router.Get("/classic/rules", readyf(h.rules))
	router.Get("/classic/targets", readyf(h.targets))
	router.Get("/classic/service-discovery", readyf(h.serviceDiscovery))
	router.Get("/classic/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = path.Join("/static", route.Param(r.Context(), "filepath"))
		fs := server.StaticFileServer(ui.Assets)
		fs.ServeHTTP(w, r)
	})
	// Make sure that "<path-prefix>/classic" is redirected to "<path-prefix>/classic/" and
	// not just the naked "/classic/", which would be the default behavior of the router
	// with the "RedirectTrailingSlash" option (https://godoc.org/github.com/julienschmidt/httprouter#Router.RedirectTrailingSlash),
	// and which breaks users with a --web.route-prefix that deviates from the path derived
	// from the external URL.
	// See https://github.com/prometheus/prometheus/issues/6163#issuecomment-553855129.
	router.Get("/classic", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, path.Join(o.ExternalURL.Path, "classic")+"/", http.StatusFound)
	})

	router.Get("/version", h.version)
	router.Get("/metrics", promhttp.Handler().ServeHTTP)

	router.Get("/federate", readyf(httputil.CompressionHandler{
		Handler: http.HandlerFunc(h.federation),
	}.ServeHTTP))

	router.Get("/consoles/*filepath", readyf(h.consoles))

	serveReactApp := func(w http.ResponseWriter, r *http.Request) {
		f, err := ui.Assets.Open("/static/react/index.html")
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error opening React index.html: %v", err)
			return
		}
		idx, err := ioutil.ReadAll(f)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, "Error reading React index.html: %v", err)
			return
		}
		replacedIdx := bytes.ReplaceAll(idx, []byte("CONSOLES_LINK_PLACEHOLDER"), []byte(h.consolesPath()))
		replacedIdx = bytes.ReplaceAll(replacedIdx, []byte("TITLE_PLACEHOLDER"), []byte(h.options.PageTitle))
		w.Write(replacedIdx)
	}

	// Serve the React app.
	for _, p := range reactRouterPaths {
		router.Get(p, serveReactApp)
	}

	// The favicon and manifest are bundled as part of the React app, but we want to serve
	// them on the root.
	for _, p := range []string{"/favicon.ico", "/manifest.json"} {
		assetPath := "/static/react" + p
		router.Get(p, func(w http.ResponseWriter, r *http.Request) {
			r.URL.Path = assetPath
			fs := server.StaticFileServer(ui.Assets)
			fs.ServeHTTP(w, r)
		})
	}

	// Static files required by the React app.
	router.Get("/static/*filepath", func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = path.Join("/static/react/static", route.Param(r.Context(), "filepath"))
		fs := server.StaticFileServer(ui.Assets)
		fs.ServeHTTP(w, r)
	})

	if o.UserAssetsPath != "" {
		router.Get("/user/*filepath", route.FileServe(o.UserAssetsPath))
	}

	if o.EnableLifecycle {
		router.Post("/-/quit", h.quit)
		router.Put("/-/quit", h.quit)
		router.Post("/-/reload", h.reload)
		router.Put("/-/reload", h.reload)
	} else {
		forbiddenAPINotEnabled := func(w http.ResponseWriter, _ *http.Request) {
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte("Lifecycle API is not enabled."))
		}
		router.Post("/-/quit", forbiddenAPINotEnabled)
		router.Put("/-/quit", forbiddenAPINotEnabled)
		router.Post("/-/reload", forbiddenAPINotEnabled)
		router.Put("/-/reload", forbiddenAPINotEnabled)
	}
	router.Get("/-/quit", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Only POST or PUT requests allowed"))
	})
	router.Get("/-/reload", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusMethodNotAllowed)
		w.Write([]byte("Only POST or PUT requests allowed"))
	})

	router.Get("/debug/*subpath", serveDebug)
	router.Post("/debug/*subpath", serveDebug)

	router.Get("/-/healthy", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, "Prometheus is Healthy.\n")
	})
	router.Get("/-/ready", readyf(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintf(w, "Prometheus is Ready.\n")
	}))

	return h
}

--- a/web/web.go
+++ b/web/web.go
@@ -99,7 +99,7 @@
 	// Redirect the original React UI's path (under "/new") to its new path at the root.
 	router.Get("/new/*path", func(w http.ResponseWriter, r *http.Request) {
 		p := route.Param(r.Context(), "path")
-		http.Redirect(w, r, path.Join(o.ExternalURL.Path, strings.TrimPrefix(p, "/new"))+"?"+r.URL.RawQuery, http.StatusFound)
+		http.Redirect(w, r, path.Join(o.ExternalURL.Path, p)+"?"+r.URL.RawQuery, http.StatusFound)
 	})

 	router.Get("/classic/alerts", readyf(h.alerts))


In lunary-ai/lunary version v1.2.13, an incorrect authorization vulnerability exists that allows unauthorized users to access and manipulate projects within an organization they should not have access to. Specifically, the vulnerability is located in the `checkProjectAccess` method within the authorization middleware, which fails to adequately verify if a user has the correct permissions to access a specific project. Instead, it only checks if the user is part of the organization owning the project, overlooking the necessary check against the `account_project` table for explicit project access rights. This flaw enables attackers to gain complete control over all resources within a project, including the ability to create, update, read, and delete any resource, compromising the privacy and security of sensitive information.

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.

export async function checkProjectAccess(projectId: string, userId: string) {
  const [{ exists: hasAccess }] = await sql`
      select exists (
        select 1
        from project
        where org_id = (select org_id from account where id = ${userId})
          and id = ${projectId}
      )
    `
  return hasAccess
}

export async function checkProjectAccess(projectId: string, userId: string) {
  const [{ exists: hasAccess }] = await sql`
      select exists (
        select 1
        from account_project ap
        where ap.project_id = ${projectId} and ap.account_id = ${userId}
      )
  `
  return hasAccess
}

--- a/packages/backend/src/utils/authorization.ts
+++ b/packages/backend/src/utils/authorization.ts
@@ -2,10 +2,9 @@
   const [{ exists: hasAccess }] = await sql`
       select exists (
         select 1
-        from project
-        where org_id = (select org_id from account where id = ${userId})
-          and id = ${projectId}
+        from account_project ap
+        where ap.project_id = ${projectId} and ap.account_id = ${userId}
       )
-    `
+  `
   return hasAccess
 }


A Code Injection exists in treekill on Windows which allows a remote code execution when an attacker is able to control the input into the command.

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

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

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

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

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

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

module.exports = function (pid, signal, callback) {
    var tree = {};
    var pidsToProcess = {};
    tree[pid] = [];
    pidsToProcess[pid] = 1;

    if (typeof signal === 'function' && callback === undefined) {
      callback = signal;
      signal = undefined;
    }

    switch (process.platform) {
    case 'win32':
        exec('taskkill /pid ' + pid + ' /T /F', callback);
        break;
    case 'darwin':
        buildProcessTree(pid, tree, pidsToProcess, function (parentPid) {
          return spawn('pgrep', ['-P', parentPid]);
        }, function () {
            killAll(tree, signal, callback);
        });
        break;
    // case 'sunos':
    //     buildProcessTreeSunOS(pid, tree, pidsToProcess, function () {
    //         killAll(tree, signal, callback);
    //     });
    //     break;
    default: // Linux
        buildProcessTree(pid, tree, pidsToProcess, function (parentPid) {
          return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]);
        }, function () {
            killAll(tree, signal, callback);
        });
        break;
    }
};

module.exports = function (pid, signal, callback) {
    if (typeof pid !== "number") {
        throw new Error("pid must be a number");
    }

    var tree = {};
    var pidsToProcess = {};
    tree[pid] = [];
    pidsToProcess[pid] = 1;

    if (typeof signal === 'function' && callback === undefined) {
      callback = signal;
      signal = undefined;
    }

    switch (process.platform) {
    case 'win32':
        exec('taskkill /pid ' + pid + ' /T /F', callback);
        break;
    case 'darwin':
        buildProcessTree(pid, tree, pidsToProcess, function (parentPid) {
          return spawn('pgrep', ['-P', parentPid]);
        }, function () {
            killAll(tree, signal, callback);
        });
        break;
    // case 'sunos':
    //     buildProcessTreeSunOS(pid, tree, pidsToProcess, function () {
    //         killAll(tree, signal, callback);
    //     });
    //     break;
    default: // Linux
        buildProcessTree(pid, tree, pidsToProcess, function (parentPid) {
          return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]);
        }, function () {
            killAll(tree, signal, callback);
        });
        break;
    }
};

--- a/index.js
+++ b/index.js
@@ -1,4 +1,8 @@
 module.exports = function (pid, signal, callback) {
+    if (typeof pid !== "number") {
+        throw new Error("pid must be a number");
+    }
+
     var tree = {};
     var pidsToProcess = {};
     tree[pid] = [];


@fastify/bearer-auth is a Fastify plugin to require bearer Authorization headers. @fastify/bearer-auth prior to versions 7.0.2 and 8.0.1 does not securely use crypto.timingSafeEqual. A malicious attacker could estimate the length of one valid bearer token. According to the corresponding RFC 6750, the bearer token has only base64 valid characters, reducing the range of characters for a brute force attack. Version 7.0.2 and 8.0.1 of @fastify/bearer-auth contain a patch. There are currently no known workarounds. The package fastify-bearer-auth, which covers versions 6.0.3 and prior, is also vulnerable starting at version 5.0.1. Users of fastify-bearer-auth should upgrade to a patched version of @fastify/bearer-auth.

Observable Discrepancy

The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor, which exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not.

function authenticate (keys, key) {
  return keys.findIndex((a) => compare(a, key)) !== -1
}

// perform constant-time comparison to prevent timing attacks
function compare (a, b) {
  try {
    // may throw if they have different length, can't convert to Buffer, etc...
    return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))
  } catch {
    return false
  }
}

function authenticate (keys, key) {
  const b = Buffer.from(key)
  return keys.findIndex((a) => compare(a, b)) !== -1
}

// perform constant-time comparison to prevent timing attacks
function compare (a, b) {
  if (a.length !== b.length) {
    // Delay return with cryptographically secure timing check.
    crypto.timingSafeEqual(a, a)
    return false
  }

  return crypto.timingSafeEqual(a, b)
}

function verifyBearerAuthFactory (options) {
  const _options = Object.assign({}, defaultOptions, options)
  if (_options.keys instanceof Set) _options.keys = Array.from(_options.keys)
  const { keys, errorResponse, contentType, bearerType, auth, addHook = true, verifyErrorLogLevel = 'error' } = _options

  for (let i = 0, il = keys.length; i < il; ++i) {
    if (typeof keys[i] !== 'string') {
      throw new Error('options.keys has to contain only string entries')
    }
    keys[i] = Buffer.from(keys[i])
  }

  return function verifyBearerAuth (request, reply, done) {
    const header = request.raw.headers.authorization
    if (!header) {
      const noHeaderError = Error('missing authorization header')
      if (verifyErrorLogLevel) request.log[verifyErrorLogLevel]('unauthorized: %s', noHeaderError.message)
      if (contentType) reply.header('content-type', contentType)
      reply.code(401)
      if (!addHook) {
        done(noHeaderError)
        return
      }
      reply.send(errorResponse(noHeaderError))
      return
    }

    const key = header.substring(bearerType.length).trim()
    let retVal
    // check if auth function is defined
    if (auth && auth instanceof Function) {
      try {
        retVal = auth(key, request)
        // catch any error from the user provided function
      } catch (err) {
        retVal = Promise.reject(err)
      }
    } else {
      // if auth is not defined use keys
      retVal = authenticate(keys, key)
    }

    const invalidKeyError = Error('invalid authorization header')

    // retVal contains the result of the auth function if defined or the
    // result of the key comparison.
    // retVal is enclosed in a Promise.resolve to allow auth to be a normal
    // function or an async funtion. If it returns a non-promise value it
    // will be converted to a resolving promise. If it returns a promise it
    // will be resolved.
    Promise.resolve(retVal).then((val) => {
      // if val is not truthy return 401
      if (val === false) {
        if (verifyErrorLogLevel) request.log[verifyErrorLogLevel]('unauthorized: %s', invalidKeyError.message)
        if (contentType) reply.header('content-type', contentType)
        reply.code(401)
        if (!addHook) return done(invalidKeyError)
        reply.send(errorResponse(invalidKeyError))
        return
      }
      if (val === true) {
        // if it fails down stream return the proper error
        try {
          done()
        } catch (err) {
          done(err)
        }
        return
      }
      const retErr = new Error('internal server error')
      reply.code(500)
      if (!addHook) return done(retErr)
      reply.send(errorResponse(retErr))
    }).catch((err) => {
      const retErr = err instanceof Error ? err : Error(String(err))
      reply.code(500)
      if (!addHook) return done(retErr)
      reply.send(errorResponse(retErr))
    })
  }
}

--- a/plugin.js
+++ b/plugin.js
@@ -1,13 +1,15 @@
 function authenticate (keys, key) {
-  return keys.findIndex((a) => compare(a, key)) !== -1
+  const b = Buffer.from(key)
+  return keys.findIndex((a) => compare(a, b)) !== -1
 }

 // perform constant-time comparison to prevent timing attacks
 function compare (a, b) {
-  try {
-    // may throw if they have different length, can't convert to Buffer, etc...
-    return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))
-  } catch {
+  if (a.length !== b.length) {
+    // Delay return with cryptographically secure timing check.
+    crypto.timingSafeEqual(a, a)
     return false
   }
+
+  return crypto.timingSafeEqual(a, b)
 }

--- /dev/null
+++ b/plugin.js
@@ -0,0 +1,81 @@
+function verifyBearerAuthFactory (options) {
+  const _options = Object.assign({}, defaultOptions, options)
+  if (_options.keys instanceof Set) _options.keys = Array.from(_options.keys)
+  const { keys, errorResponse, contentType, bearerType, auth, addHook = true, verifyErrorLogLevel = 'error' } = _options
+
+  for (let i = 0, il = keys.length; i < il; ++i) {
+    if (typeof keys[i] !== 'string') {
+      throw new Error('options.keys has to contain only string entries')
+    }
+    keys[i] = Buffer.from(keys[i])
+  }
+
+  return function verifyBearerAuth (request, reply, done) {
+    const header = request.raw.headers.authorization
+    if (!header) {
+      const noHeaderError = Error('missing authorization header')
+      if (verifyErrorLogLevel) request.log[verifyErrorLogLevel]('unauthorized: %s', noHeaderError.message)
+      if (contentType) reply.header('content-type', contentType)
+      reply.code(401)
+      if (!addHook) {
+        done(noHeaderError)
+        return
+      }
+      reply.send(errorResponse(noHeaderError))
+      return
+    }
+
+    const key = header.substring(bearerType.length).trim()
+    let retVal
+    // check if auth function is defined
+    if (auth && auth instanceof Function) {
+      try {
+        retVal = auth(key, request)
+        // catch any error from the user provided function
+      } catch (err) {
+        retVal = Promise.reject(err)
+      }
+    } else {
+      // if auth is not defined use keys
+      retVal = authenticate(keys, key)
+    }
+
+    const invalidKeyError = Error('invalid authorization header')
+
+    // retVal contains the result of the auth function if defined or the
+    // result of the key comparison.
+    // retVal is enclosed in a Promise.resolve to allow auth to be a normal
+    // function or an async funtion. If it returns a non-promise value it
+    // will be converted to a resolving promise. If it returns a promise it
+    // will be resolved.
+    Promise.resolve(retVal).then((val) => {
+      // if val is not truthy return 401
+      if (val === false) {
+        if (verifyErrorLogLevel) request.log[verifyErrorLogLevel]('unauthorized: %s', invalidKeyError.message)
+        if (contentType) reply.header('content-type', contentType)
+        reply.code(401)
+        if (!addHook) return done(invalidKeyError)
+        reply.send(errorResponse(invalidKeyError))
+        return
+      }
+      if (val === true) {
+        // if it fails down stream return the proper error
+        try {
+          done()
+        } catch (err) {
+          done(err)
+        }
+        return
+      }
+      const retErr = new Error('internal server error')
+      reply.code(500)
+      if (!addHook) return done(retErr)
+      reply.send(errorResponse(retErr))
+    }).catch((err) => {
+      const retErr = err instanceof Error ? err : Error(String(err))
+      reply.code(500)
+      if (!addHook) return done(retErr)
+      reply.send(errorResponse(retErr))
+    })
+  }
+}


Roblox-Purchasing-Hub is an open source Roblox product purchasing hub. A security risk in versions 1.0.1 and prior allowed people who have someone's API URL to get product files without an API key. This issue is fixed in version 1.0.2. As a workaround, add `@require_apikey` in `BOT/lib/cogs/website.py` under the route for `/v1/products`.

Improper Encoding or Escaping of Output

The product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. As a result, the intended structure of the message is not preserved.

@app.route("/v1/products", methods=["GET"])
async def products():
    dbresponse = getproducts()
    results = {}
    for i in dbresponse:
        results[i["name"]] = i
    return results

@app.route("/v1/products", methods=["GET"])
@require_apikey
async def products():
    dbresponse = getproducts()
    results = {}
    for i in dbresponse:
        results[i["name"]] = i
    return results

--- a/BOT/lib/cogs/website.py
+++ b/BOT/lib/cogs/website.py
@@ -1,4 +1,5 @@
 @app.route("/v1/products", methods=["GET"])
+@require_apikey
 async def products():
     dbresponse = getproducts()
     results = {}


Collabora Online is a collaborative online office suite based on LibreOffice technology. Any deployment of voilà dashboard allow local file inclusion. Any file on a filesystem that is readable by the user that runs the voilà dashboard server can be downloaded by someone with network access to the server. Whether this still requires authentication depends on how voilà is deployed. This issue has been patched in 0.2.17, 0.3.8, 0.4.4 and 0.5.6.

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 init_settings(self) -> Dict:
        """Initialize settings for Voila application."""
        # default server_url to base_url
        self.server_url = self.server_url or self.base_url

        self.kernel_spec_manager = self.voila_configuration.kernel_spec_manager_class(
            parent=self
        )

        # we create a config manager that load both the serverconfig and nbconfig (classical notebook)
        read_config_path = [
            os.path.join(p, "serverconfig") for p in jupyter_config_path()
        ]
        read_config_path += [os.path.join(p, "nbconfig") for p in jupyter_config_path()]
        self.config_manager = ConfigManager(
            parent=self, read_config_path=read_config_path
        )
        self.contents_manager = LargeFileManager(parent=self)
        preheat_kernel: bool = self.voila_configuration.preheat_kernel
        pool_size: int = self.voila_configuration.default_pool_size

        if preheat_kernel and self.prelaunch_hook:
            raise Exception("`preheat_kernel` and `prelaunch_hook` are incompatible")

        kernel_manager_class = voila_kernel_manager_factory(
            self.voila_configuration.multi_kernel_manager_class,
            preheat_kernel,
            pool_size,
        )
        self.kernel_manager = kernel_manager_class(
            parent=self,
            connection_dir=self.connection_dir,
            kernel_spec_manager=self.kernel_spec_manager,
            allowed_message_types=[
                "comm_open",
                "comm_close",
                "comm_msg",
                "comm_info_request",
                "kernel_info_request",
                "shutdown_request",
            ],
        )

        jenv_opt = {
            "autoescape": True
        }  # we might want extra options via cmd line like notebook server
        env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(self.template_paths),
            extensions=["jinja2.ext.i18n"],
            **jenv_opt,
        )
        server_env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(DEFAULT_TEMPLATE_PATH_LIST),
            extensions=["jinja2.ext.i18n"],
            **jenv_opt,
        )

        nbui = gettext.translation(
            "nbui", localedir=os.path.join(ROOT, "i18n"), fallback=True
        )
        env.install_gettext_translations(nbui, newstyle=False)
        server_env.install_gettext_translations(nbui, newstyle=False)

        identity_provider_kwargs = {
            "parent": self,
            "log": self.log,
            "login_handler_class": VoilaLoginHandler,
        }
        if self.token is None and not self.auto_token:
            identity_provider_kwargs["token"] = ""
        elif self.token is not None:
            identity_provider_kwargs["token"] = self.token

        self.identity_provider = self.identity_provider_class(
            **identity_provider_kwargs
        )

        self.authorizer = self.authorizer_class(
            parent=self, log=self.log, identity_provider=self.identity_provider
        )

        settings = dict(
            base_url=self.base_url,
            server_url=self.server_url or self.base_url,
            kernel_manager=self.kernel_manager,
            kernel_spec_manager=self.kernel_spec_manager,
            allow_remote_access=True,
            autoreload=self.autoreload,
            voila_jinja2_env=env,
            jinja2_env=server_env,
            static_path="/",
            server_root_dir="/",
            contents_manager=self.contents_manager,
            config_manager=self.config_manager,
            cookie_secret=self.cookie_secret,
            authorizer=self.authorizer,
            identity_provider=self.identity_provider,
            kernel_websocket_connection_class=self.kernel_websocket_connection_class,
            login_url=url_path_join(self.base_url, "/login"),
            mathjax_config=self.mathjax_config,
            mathjax_url=self.mathjax_url,
        )
        settings[self.name] = self  # Why???

        return settings

    def init_settings(self) -> Dict:
        """Initialize settings for Voila application."""
        # default server_url to base_url
        self.server_url = self.server_url or self.base_url

        self.kernel_spec_manager = self.voila_configuration.kernel_spec_manager_class(
            parent=self
        )

        # we create a config manager that load both the serverconfig and nbconfig (classical notebook)
        read_config_path = [
            os.path.join(p, "serverconfig") for p in jupyter_config_path()
        ]
        read_config_path += [os.path.join(p, "nbconfig") for p in jupyter_config_path()]
        self.config_manager = ConfigManager(
            parent=self, read_config_path=read_config_path
        )
        self.contents_manager = LargeFileManager(parent=self)
        preheat_kernel: bool = self.voila_configuration.preheat_kernel
        pool_size: int = self.voila_configuration.default_pool_size

        if preheat_kernel and self.prelaunch_hook:
            raise Exception("`preheat_kernel` and `prelaunch_hook` are incompatible")

        kernel_manager_class = voila_kernel_manager_factory(
            self.voila_configuration.multi_kernel_manager_class,
            preheat_kernel,
            pool_size,
        )
        self.kernel_manager = kernel_manager_class(
            parent=self,
            connection_dir=self.connection_dir,
            kernel_spec_manager=self.kernel_spec_manager,
            allowed_message_types=[
                "comm_open",
                "comm_close",
                "comm_msg",
                "comm_info_request",
                "kernel_info_request",
                "shutdown_request",
            ],
        )

        jenv_opt = {
            "autoescape": True
        }  # we might want extra options via cmd line like notebook server
        env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(self.template_paths),
            extensions=["jinja2.ext.i18n"],
            **jenv_opt,
        )
        server_env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(DEFAULT_TEMPLATE_PATH_LIST),
            extensions=["jinja2.ext.i18n"],
            **jenv_opt,
        )

        nbui = gettext.translation(
            "nbui", localedir=os.path.join(ROOT, "i18n"), fallback=True
        )
        env.install_gettext_translations(nbui, newstyle=False)
        server_env.install_gettext_translations(nbui, newstyle=False)

        identity_provider_kwargs = {
            "parent": self,
            "log": self.log,
            "login_handler_class": VoilaLoginHandler,
        }
        if self.token is None and not self.auto_token:
            identity_provider_kwargs["token"] = ""
        elif self.token is not None:
            identity_provider_kwargs["token"] = self.token

        self.identity_provider = self.identity_provider_class(
            **identity_provider_kwargs
        )

        self.authorizer = self.authorizer_class(
            parent=self, log=self.log, identity_provider=self.identity_provider
        )

        settings = dict(
            base_url=self.base_url,
            server_url=self.server_url or self.base_url,
            kernel_manager=self.kernel_manager,
            kernel_spec_manager=self.kernel_spec_manager,
            allow_remote_access=True,
            autoreload=self.autoreload,
            voila_jinja2_env=env,
            jinja2_env=server_env,
            server_root_dir="/",
            contents_manager=self.contents_manager,
            config_manager=self.config_manager,
            cookie_secret=self.cookie_secret,
            authorizer=self.authorizer,
            identity_provider=self.identity_provider,
            kernel_websocket_connection_class=self.kernel_websocket_connection_class,
            login_url=url_path_join(self.base_url, "/login"),
            mathjax_config=self.mathjax_config,
            mathjax_url=self.mathjax_url,
        )
        settings[self.name] = self  # Why???

        return settings

--- a/voila/app.py
+++ b/voila/app.py
@@ -88,7 +88,6 @@
             autoreload=self.autoreload,
             voila_jinja2_env=env,
             jinja2_env=server_env,
-            static_path="/",
             server_root_dir="/",
             contents_manager=self.contents_manager,
             config_manager=self.config_manager,


Directus is a real-time API and App dashboard for managing SQL database content. Prior to version 9.23.3, the `directus_refresh_token` is not redacted properly from the log outputs and can be used to impersonate users without their permission. This issue is patched in version 9.23.3.

Insertion of Sensitive Information into Log File

The product writes sensitive information to a log file.

export const expressLogger = pinoHTTP({
	logger: pino(merge(httpLoggerOptions, loggerEnvConfig)),
	...httpLoggerEnvConfig,
	serializers: {
		req(request: Request) {
			const output = stdSerializers.req(request);
			output.url = redactQuery(output.url);
			return output;
		},
	},
}) as RequestHandler;

export const expressLogger = pinoHTTP({
	logger: pino(merge(httpLoggerOptions, loggerEnvConfig)),
	...httpLoggerEnvConfig,
	serializers: {
		req(request: Request) {
			const output = stdSerializers.req(request);
			output.url = redactQuery(output.url);
			if (output.headers?.cookie) {
				output.headers.cookie = redactHeaderCookie(output.headers.cookie, [
					'access_token',
					`${env.REFRESH_TOKEN_COOKIE_NAME}`,
				]);
			}
			return output;
		},
		res(response: SerializedResponse) {
			if (response.headers?.['set-cookie']) {
				response.headers['set-cookie'] = redactHeaderCookie(response.headers['set-cookie'], [
					'access_token',
					`${env.REFRESH_TOKEN_COOKIE_NAME}`,
				]);
			}
			return response;
		},
	},
}) as RequestHandler;

export function redactHeaderCookie(cookieHeader: string, cookieNames: string[]) {
	for (const cookieName of cookieNames) {
		const re = new RegExp(`(${cookieName}=)([^;]+)`);
		cookieHeader = cookieHeader.replace(re, `$1--redacted--`);
	}
	return cookieHeader;
}

--- a/api/src/logger.ts
+++ b/api/src/logger.ts
@@ -5,7 +5,22 @@
 		req(request: Request) {
 			const output = stdSerializers.req(request);
 			output.url = redactQuery(output.url);
+			if (output.headers?.cookie) {
+				output.headers.cookie = redactHeaderCookie(output.headers.cookie, [
+					'access_token',
+					`${env.REFRESH_TOKEN_COOKIE_NAME}`,
+				]);
+			}
 			return output;
+		},
+		res(response: SerializedResponse) {
+			if (response.headers?.['set-cookie']) {
+				response.headers['set-cookie'] = redactHeaderCookie(response.headers['set-cookie'], [
+					'access_token',
+					`${env.REFRESH_TOKEN_COOKIE_NAME}`,
+				]);
+			}
+			return response;
 		},
 	},
 }) as RequestHandler;

--- /dev/null
+++ b/api/src/logger.ts
@@ -0,0 +1,7 @@
+export function redactHeaderCookie(cookieHeader: string, cookieNames: string[]) {
+	for (const cookieName of cookieNames) {
+		const re = new RegExp(`(${cookieName}=)([^;]+)`);
+		cookieHeader = cookieHeader.replace(re, `$1--redacted--`);
+	}
+	return cookieHeader;
+}


The npm package "tar" (aka node-tar) before versions 4.4.18, 5.0.10, and 6.1.9 has an arbitrary file creation/overwrite and arbitrary code execution vulnerability. node-tar aims to guarantee that any file whose location would be outside of the extraction target directory is not extracted. This is, in part, accomplished by sanitizing absolute paths of entries within the archive, skipping archive entries that contain `..` path portions, and resolving the sanitized paths against the extraction target directory. This logic was insufficient on Windows systems when extracting tar files that contained a path that was not an absolute path, but specified a drive letter different from the extraction target, such as `C:some\path`. If the drive letter does not match the extraction target, for example `D:\extraction\dir`, then the result of `path.resolve(extractionDirectory, entryPath)` would resolve against the current working directory on the `C:` drive, rather than the extraction target directory. Additionally, a `..` portion of the path could occur immediately after the drive letter, such as `C:../foo`, and was not properly sanitized by the logic that checked for `..` within the normalized and split portions of the path. This only affects users of `node-tar` on Windows systems. These issues were addressed in releases 4.4.18, 5.0.10 and 6.1.9. The v3 branch of node-tar has been deprecated and did not receive patches for these issues. If you are still using a v3 release we recommend you update to a more recent version of node-tar. There is no reasonable way to work around this issue without performing the same path normalization procedures that node-tar now does. Users are encouraged to upgrade to the latest patched versions of node-tar, rather than attempt to sanitize paths themselves.

module.exports = path => {
  let r = ''
  while (isAbsolute(path)) {
    // windows will think that //x/y/z has a "root" of //x/y/
    const root = path.charAt(0) === '/' ? '/' : parse(path).root
    path = path.substr(root.length)
    r += root
  }
  return [r, path]
}

module.exports = path => {
  let r = ''

  let parsed = parse(path)
  while (isAbsolute(path) || parsed.root) {
    // windows will think that //x/y/z has a "root" of //x/y/
    // but strip the //?/C:/ off of //?/C:/path
    const root = path.charAt(0) === '/' && path.slice(0, 4) !== '//?/' ? '/'
      : parsed.root
    path = path.substr(root.length)
    r += root
    parsed = parse(path)
  }
  return [r, path]
}

--- a/lib/strip-absolute-path.js
+++ b/lib/strip-absolute-path.js
@@ -1,10 +1,15 @@
 module.exports = path => {
   let r = ''
-  while (isAbsolute(path)) {
+
+  let parsed = parse(path)
+  while (isAbsolute(path) || parsed.root) {
     // windows will think that //x/y/z has a "root" of //x/y/
-    const root = path.charAt(0) === '/' ? '/' : parse(path).root
+    // but strip the //?/C:/ off of //?/C:/path
+    const root = path.charAt(0) === '/' && path.slice(0, 4) !== '//?/' ? '/'
+      : parsed.root
     path = path.substr(root.length)
     r += root
+    parsed = parse(path)
   }
   return [r, path]
 }

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

cd /workspace/node-tar
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
node test/strip-absolute-path.js

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

cd /workspace/node-tar
git apply --whitespace=nowarn /workspace/fix.patch
node test/strip-absolute-path.js


Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. When either the Helm client or SDK is used to save a chart whose name within the `Chart.yaml` file includes a relative path change, the chart would be saved outside its expected directory based on the changes in the relative path. The validation and linting did not detect the path changes in the name. This issue has been resolved in Helm v3.14.1. Users unable to upgrade should check all charts used by Helm for path changes in their name as found in the `Chart.yaml` file. This includes dependencies.

func (md *Metadata) Validate() error {
	if md == nil {
		return ValidationError("chart.metadata is required")
	}

	md.Name = sanitizeString(md.Name)
	md.Description = sanitizeString(md.Description)
	md.Home = sanitizeString(md.Home)
	md.Icon = sanitizeString(md.Icon)
	md.Condition = sanitizeString(md.Condition)
	md.Tags = sanitizeString(md.Tags)
	md.AppVersion = sanitizeString(md.AppVersion)
	md.KubeVersion = sanitizeString(md.KubeVersion)
	for i := range md.Sources {
		md.Sources[i] = sanitizeString(md.Sources[i])
	}
	for i := range md.Keywords {
		md.Keywords[i] = sanitizeString(md.Keywords[i])
	}

	if md.APIVersion == "" {
		return ValidationError("chart.metadata.apiVersion is required")
	}
	if md.Name == "" {
		return ValidationError("chart.metadata.name is required")
	}
	if md.Version == "" {
		return ValidationError("chart.metadata.version is required")
	}
	if !isValidSemver(md.Version) {
		return ValidationErrorf("chart.metadata.version %q is invalid", md.Version)
	}
	if !isValidChartType(md.Type) {
		return ValidationError("chart.metadata.type must be application or library")
	}

	for _, m := range md.Maintainers {
		if err := m.Validate(); err != nil {
			return err
		}
	}

	// Aliases need to be validated here to make sure that the alias name does
	// not contain any illegal characters.
	dependencies := map[string]*Dependency{}
	for _, dependency := range md.Dependencies {
		if err := dependency.Validate(); err != nil {
			return err
		}
		key := dependency.Name
		if dependency.Alias != "" {
			key = dependency.Alias
		}
		if dependencies[key] != nil {
			return ValidationErrorf("more than one dependency with name or alias %q", key)
		}
		dependencies[key] = dependency
	}
	return nil
}

func (md *Metadata) Validate() error {
	if md == nil {
		return ValidationError("chart.metadata is required")
	}

	md.Name = sanitizeString(md.Name)
	md.Description = sanitizeString(md.Description)
	md.Home = sanitizeString(md.Home)
	md.Icon = sanitizeString(md.Icon)
	md.Condition = sanitizeString(md.Condition)
	md.Tags = sanitizeString(md.Tags)
	md.AppVersion = sanitizeString(md.AppVersion)
	md.KubeVersion = sanitizeString(md.KubeVersion)
	for i := range md.Sources {
		md.Sources[i] = sanitizeString(md.Sources[i])
	}
	for i := range md.Keywords {
		md.Keywords[i] = sanitizeString(md.Keywords[i])
	}

	if md.APIVersion == "" {
		return ValidationError("chart.metadata.apiVersion is required")
	}
	if md.Name == "" {
		return ValidationError("chart.metadata.name is required")
	}

	if md.Name != filepath.Base(md.Name) {
		return ValidationErrorf("chart.metadata.name %q is invalid", md.Name)
	}

	if md.Version == "" {
		return ValidationError("chart.metadata.version is required")
	}
	if !isValidSemver(md.Version) {
		return ValidationErrorf("chart.metadata.version %q is invalid", md.Version)
	}
	if !isValidChartType(md.Type) {
		return ValidationError("chart.metadata.type must be application or library")
	}

	for _, m := range md.Maintainers {
		if err := m.Validate(); err != nil {
			return err
		}
	}

	// Aliases need to be validated here to make sure that the alias name does
	// not contain any illegal characters.
	dependencies := map[string]*Dependency{}
	for _, dependency := range md.Dependencies {
		if err := dependency.Validate(); err != nil {
			return err
		}
		key := dependency.Name
		if dependency.Alias != "" {
			key = dependency.Alias
		}
		if dependencies[key] != nil {
			return ValidationErrorf("more than one dependency with name or alias %q", key)
		}
		dependencies[key] = dependency
	}
	return nil
}

--- a/pkg/chart/metadata.go
+++ b/pkg/chart/metadata.go
@@ -24,6 +24,11 @@
 	if md.Name == "" {
 		return ValidationError("chart.metadata.name is required")
 	}
+
+	if md.Name != filepath.Base(md.Name) {
+		return ValidationErrorf("chart.metadata.name %q is invalid", md.Name)
+	}
+
 	if md.Version == "" {
 		return ValidationError("chart.metadata.version is required")
 	}

--- /dev/null
+++ b/pkg/chart/metadata.go
@@ -0,0 +1 @@
+	"path/filepath"

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

cd /workspace/helm
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test    -timeout 30s -run ^TestValidate$ helm.sh/helm/v3/pkg/chart

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

cd /workspace/helm
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestBuild_WithoutOptionalFields|TestSaveDir|TestKey|TestNormalizeURL|TestValidateChartVersion|TestBuild_WithCondition|TestVersionEquals|TestValidateChartName|TestSavePreservesTimestamps|TestValidateChartYamlFormat|TestChartfile|TestValidateChartSources|TestValidate_sanitize|TestGetRepoNames|TestBuild_WithRepositoryAlias|TestErrRepoNotFound_Error|TestBuild_WithSemVerRange|TestValidateChartYamlNotDirectory|TestValidate|TestSave|TestFindChartURL|TestBuild_WithTags|TestBuild_WithAlias|TestValidateChartIconURL|TestValidateChartIconPresence|TestUpdateWithNoRepo|TestUpdateBeforeBuild|TestValidateChartMaintainer|TestDownloadAll)$' helm.sh/helm/v3/pkg/chart


Versions of the package network before 0.7.0 are vulnerable to Arbitrary Command Injection due to use of the child_process exec function without input sanitization. If (attacker-controlled) user input is given to the mac_address_for function of the package, it is possible for the attacker to execute arbitrary commands on the operating system that this package is being run on.

exports.get_active_network_interface_name = function(cb) {
  var cmd = "netstat -rn | grep UG | awk '{print $NF}'";
  exec(cmd, function(err, stdout) {
    if (err) return cb(err);

    var raw = stdout.toString().trim().split('\n');
    if (raw.length === 0 || raw === [''])
      return cb(new Error('No active network interface found.'));

    cb(null, raw[0]);
  });
};

exports.interface_type_for = function(nic_name, cb) {
  exec('cat /proc/net/wireless | grep ' + nic_name, function(err, out) {
    return cb(null, err ? 'Wired' : 'Wireless')
  })
};

exports.mac_address_for = function(nic_name, cb) {
  var cmd = 'cat /sys/class/net/' + nic_name + '/address';
  trim_exec(cmd, cb);
};

exports.gateway_ip_for = function(nic_name, cb) {
  trim_exec("ip r | grep " + nic_name + " | grep default | cut -d ' ' -f 3 | head -n1", cb);
};

exports.netmask_for = function(nic_name, cb) {
  var cmd = "ifconfig " + nic_name + " 2> /dev/null | egrep 'netmask|Mask:' | awk '{print $4}' | sed 's/Mask://'";
  trim_exec(cmd, cb);
};

exports.get_active_network_interface_name = function(cb) {
  var cmd = "netstat -rn | grep UG | awk '{print $NF}'";
  exec(cmd, function(err, stdout, stderr) {
    if (err) return cb(err);

    if (stderr.toString().match('not found')) {
      return cb(new Error('Command failed: ' + stderr.toString().trim()))
    }

    var raw = stdout.toString().trim().split('\n');
    if (raw.length === 0 || raw === [''])
      return cb(new Error('No active network interface found.'));

    cb(null, raw[0]);
  });
};

exports.interface_type_for = function(nic_name, cb) {
  ensure_valid_nic(nic_name);
  exec('cat /proc/net/wireless | grep ' + nic_name, function(err, out) {
    return cb(null, err ? 'Wired' : 'Wireless')
  })
};

exports.mac_address_for = function(nic_name, cb) {
  ensure_valid_nic(nic_name);
  var cmd = 'cat /sys/class/net/' + nic_name + '/address';
  trim_exec(cmd, cb);
};

exports.gateway_ip_for = function(nic_name, cb) {
  ensure_valid_nic(nic_name);
  trim_exec("ip r | grep " + nic_name + " | grep default | cut -d ' ' -f 3 | head -n1", cb);
};

exports.netmask_for = function(nic_name, cb) {
  ensure_valid_nic(nic_name);
  var cmd = "ifconfig " + nic_name + " 2> /dev/null | egrep 'netmask|Mask:' | awk '{print $4}' | sed 's/Mask://'";
  trim_exec(cmd, cb);
};

--- a/lib/linux.js
+++ b/lib/linux.js
@@ -1,7 +1,11 @@
 exports.get_active_network_interface_name = function(cb) {
   var cmd = "netstat -rn | grep UG | awk '{print $NF}'";
-  exec(cmd, function(err, stdout) {
+  exec(cmd, function(err, stdout, stderr) {
     if (err) return cb(err);
+
+    if (stderr.toString().match('not found')) {
+      return cb(new Error('Command failed: ' + stderr.toString().trim()))
+    }

     var raw = stdout.toString().trim().split('\n');
     if (raw.length === 0 || raw === [''])
@@ -12,21 +16,25 @@
 };

 exports.interface_type_for = function(nic_name, cb) {
+  ensure_valid_nic(nic_name);
   exec('cat /proc/net/wireless | grep ' + nic_name, function(err, out) {
     return cb(null, err ? 'Wired' : 'Wireless')
   })
 };

 exports.mac_address_for = function(nic_name, cb) {
+  ensure_valid_nic(nic_name);
   var cmd = 'cat /sys/class/net/' + nic_name + '/address';
   trim_exec(cmd, cb);
 };

 exports.gateway_ip_for = function(nic_name, cb) {
+  ensure_valid_nic(nic_name);
   trim_exec("ip r | grep " + nic_name + " | grep default | cut -d ' ' -f 3 | head -n1", cb);
 };

 exports.netmask_for = function(nic_name, cb) {
+  ensure_valid_nic(nic_name);
   var cmd = "ifconfig " + nic_name + " 2> /dev/null | egrep 'netmask|Mask:' | awk '{print $4}' | sed 's/Mask://'";
   trim_exec(cmd, cb);
 };


resolve-path node module before 1.4.0 suffers from a Path Traversal vulnerability due to lack of validation of paths with certain special characters, which allows a malicious user to read content of any file with known path.

var createError = require('http-errors')
var normalize = require('path').normalize
var pathIsAbsolute = require('path-is-absolute')
var resolve = require('path').resolve
var sep = require('path').sep

function resolvePath (rootPath, relativePath) {
  var path = relativePath
  var root = rootPath

  // root is optional, similar to root.resolve
  if (arguments.length === 1) {
    path = rootPath
    root = process.cwd()
  }

  if (root == null) {
    throw new TypeError('argument rootPath is required')
  }

  if (typeof root !== 'string') {
    throw new TypeError('argument rootPath must be a string')
  }

  if (path == null) {
    throw new TypeError('argument relativePath is required')
  }

  if (typeof path !== 'string') {
    throw new TypeError('argument relativePath must be a string')
  }

  // containing NULL bytes is malicious
  if (path.indexOf('\0') !== -1) {
    throw createError(400, 'Malicious Path')
  }

  // path should never be absolute
  if (pathIsAbsolute.posix(path) || pathIsAbsolute.win32(path)) {
    throw createError(400, 'Malicious Path')
  }

  // path outside root
  if (UP_PATH_REGEXP.test(normalize('.' + sep + path))) {
    throw createError(403)
  }

  // resolve & normalize the root path
  root = normalize(resolve(root) + sep)

  // resolve the path
  return resolve(root, path)
}

var createError = require('http-errors')
var join = require('path').join
var normalize = require('path').normalize
var pathIsAbsolute = require('path-is-absolute')
var resolve = require('path').resolve
var sep = require('path').sep

function resolvePath (rootPath, relativePath) {
  var path = relativePath
  var root = rootPath

  // root is optional, similar to root.resolve
  if (arguments.length === 1) {
    path = rootPath
    root = process.cwd()
  }

  if (root == null) {
    throw new TypeError('argument rootPath is required')
  }

  if (typeof root !== 'string') {
    throw new TypeError('argument rootPath must be a string')
  }

  if (path == null) {
    throw new TypeError('argument relativePath is required')
  }

  if (typeof path !== 'string') {
    throw new TypeError('argument relativePath must be a string')
  }

  // containing NULL bytes is malicious
  if (path.indexOf('\0') !== -1) {
    throw createError(400, 'Malicious Path')
  }

  // path should never be absolute
  if (pathIsAbsolute.posix(path) || pathIsAbsolute.win32(path)) {
    throw createError(400, 'Malicious Path')
  }

  // path outside root
  if (UP_PATH_REGEXP.test(normalize('.' + sep + path))) {
    throw createError(403)
  }

  // join the relative path
  return normalize(join(resolve(root), path))
}

--- a/index.js
+++ b/index.js
@@ -1,4 +1,5 @@
 var createError = require('http-errors')
+var join = require('path').join
 var normalize = require('path').normalize
 var pathIsAbsolute = require('path-is-absolute')
 var resolve = require('path').resolve

--- a/index.js
+++ b/index.js
@@ -39,9 +39,6 @@
     throw createError(403)
   }

-  // resolve & normalize the root path
-  root = normalize(resolve(root) + sep)
-
-  // resolve the path
-  return resolve(root, path)
+  // join the relative path
+  return normalize(join(resolve(root), path))
 }


A vulnerability was found in docconv up to 1.2.0. It has been declared as critical. This vulnerability affects the function ConvertPDFImages of the file pdf_ocr.go. The manipulation of the argument path leads to os command injection. The attack can be initiated remotely. Upgrading to version 1.2.1 is able to address this issue. The name of the patch is b19021ade3d0b71c89d35cb00eb9e589a121faa5. It is recommended to upgrade the affected component. VDB-216502 is the identifier assigned to this vulnerability.

func PDFHasImage(path string) bool {
	cmd := "pdffonts -l 5 %s | tail -n +3 | cut -d' ' -f1 | sort | uniq"
	out, err := exec.Command("bash", "-c", fmt.Sprintf(cmd, path)).Output()
	if err != nil {
		log.Println(err)
		return false
	}
	if string(out) == "" {
		return true
	}
	return false
}

func PDFHasImage(path string) bool {
	cmd := "pdffonts -l 5 %s | tail -n +3 | cut -d' ' -f1 | sort | uniq"
	out, err := exec.Command("bash", "-c", fmt.Sprintf(cmd, shellEscape(path))).CombinedOutput()

	if err != nil {
		log.Println(err)
		return false
	}
	if string(out) == "" {
		return true
	}
	return false
}

func init() {
	shellEscapePattern = regexp.MustCompile(`[^\w@%+=:,./-]`)
}

func shellEscape(s string) string {
	if len(s) == 0 {
		return "''"
	}
	if shellEscapePattern.MatchString(s) {
		return "'" + strings.Replace(s, "'", "'\"'\"'", -1) + "'"
	}

	return s
}

--- a/pdf_ocr.go
+++ b/pdf_ocr.go
@@ -1,6 +1,7 @@
 func PDFHasImage(path string) bool {
 	cmd := "pdffonts -l 5 %s | tail -n +3 | cut -d' ' -f1 | sort | uniq"
-	out, err := exec.Command("bash", "-c", fmt.Sprintf(cmd, path)).Output()
+	out, err := exec.Command("bash", "-c", fmt.Sprintf(cmd, shellEscape(path))).CombinedOutput()
+
 	if err != nil {
 		log.Println(err)
 		return false

--- /dev/null
+++ b/pdf_ocr.go
@@ -0,0 +1 @@
+var shellEscapePattern *regexp.Regexp

--- /dev/null
+++ b/pdf_ocr.go
@@ -0,0 +1,3 @@
+func init() {
+	shellEscapePattern = regexp.MustCompile(`[^\w@%+=:,./-]`)
+}

--- /dev/null
+++ b/pdf_ocr.go
@@ -0,0 +1,10 @@
+func shellEscape(s string) string {
+	if len(s) == 0 {
+		return "''"
+	}
+	if shellEscapePattern.MatchString(s) {
+		return "'" + strings.Replace(s, "'", "'\"'\"'", -1) + "'"
+	}
+
+	return s
+}

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

cd /workspace/docconv
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -tags=ocr -timeout 30s -run '^TestPDFHasImage_CannotExecuteCode$'

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

cd /workspace/docconv
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -tags=ocr -timeout 30s -run '(^TestPDFHasImage_NoCommandInjectionSideEffects$|^TestPDFHasImage_PassesFullPath$)'


sequelize is an Object-relational mapping, or a middleman to convert things from Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server into usable data for NodeJS In Postgres, SQLite, and Microsoft SQL Server there is an issue where arrays are treated as strings and improperly escaped. This causes potential SQL injection in sequelize 3.19.3 and earlier, where a malicious user could put `["test", "'); DELETE TestTable WHERE Id = 1 --')"]` inside of ``` database.query('SELECT * FROM TestTable WHERE Name IN (:names)', { replacements: { names: directCopyOfUserInput } }); ``` and cause the SQL statement to become `SELECT Id FROM Table WHERE Name IN ('test', '\'); DELETE TestTable WHERE Id = 1 --')`. In Postgres, MSSQL, and SQLite, the backslash has no special meaning. This causes the the statement to delete whichever Id has a value of 1 in the TestTable table.

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

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

SqlString.escape = function(val, timeZone, dialect, format) {
  var prependN = false;
  if (val === undefined || val === null) {
    return 'NULL';
  }
  switch (typeof val) {
  case 'boolean':
    // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
    // for us. Postgres actually has a boolean type with true/false literals,
    // but sequelize doesn't use it yet.
    if (dialect === 'sqlite' || dialect === 'mssql') {
      return +!!val;
    }
    return '' + !!val;
  case 'number':
    return val + '';
  case 'string':
    // In mssql, prepend N to all quoted vals which are originally a string (for
    // unicode compatibility)
    prependN = dialect === 'mssql';
    break;
  }

  if (val instanceof Date) {
    val = dataTypes[dialect].DATE.prototype.stringify(val, { timezone: timeZone });
  }

  if (Buffer.isBuffer(val)) {
    if (dataTypes[dialect].BLOB) {
      return dataTypes[dialect].BLOB.prototype.stringify(val);
    }

    return dataTypes.BLOB.prototype.stringify(val);
  }

  if (Array.isArray(val)) {
    var escape = _.partialRight(SqlString.escape, timeZone, dialect);
    if (dialect === 'postgres' && !format) {
      return dataTypes.ARRAY.prototype.stringify(val, {escape: escape});
    }
    return val.map(escape);
  }

  if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') {
    // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
    // http://stackoverflow.com/q/603572/130598
    val = val.replace(/'/g, "''");
  } else {
    val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
      switch (s) {
        case '\0': return '\\0';
        case '\n': return '\\n';
        case '\r': return '\\r';
        case '\b': return '\\b';
        case '\t': return '\\t';
        case '\x1a': return '\\Z';
        default: return '\\' + s;
      }
    });
  }
  return (prependN ? "N'" : "'") + val + "'";
};

SqlString.escape = function(val, timeZone, dialect, format) {
  var prependN = false;
  if (val === undefined || val === null) {
    return 'NULL';
  }
  switch (typeof val) {
  case 'boolean':
    // SQLite doesn't have true/false support. MySQL aliases true/false to 1/0
    // for us. Postgres actually has a boolean type with true/false literals,
    // but sequelize doesn't use it yet.
    if (dialect === 'sqlite' || dialect === 'mssql') {
      return +!!val;
    }
    return '' + !!val;
  case 'number':
    return val + '';
  case 'string':
    // In mssql, prepend N to all quoted vals which are originally a string (for
    // unicode compatibility)
    prependN = dialect === 'mssql';
    break;
  }

  if (val instanceof Date) {
    val = dataTypes[dialect].DATE.prototype.stringify(val, { timezone: timeZone });
  }

  if (Buffer.isBuffer(val)) {
    if (dataTypes[dialect].BLOB) {
      return dataTypes[dialect].BLOB.prototype.stringify(val);
    }

    return dataTypes.BLOB.prototype.stringify(val);
  }

  if (Array.isArray(val)) {
    var escape = _.partial(SqlString.escape, _, timeZone, dialect, format);
    if (dialect === 'postgres' && !format) {
      return dataTypes.ARRAY.prototype.stringify(val, {escape: escape});
    }
    return val.map(escape);
  }

  if (dialect === 'postgres' || dialect === 'sqlite' || dialect === 'mssql') {
    // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
    // http://stackoverflow.com/q/603572/130598
    val = val.replace(/'/g, "''");
  } else {
    val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
      switch (s) {
        case '\0': return '\\0';
        case '\n': return '\\n';
        case '\r': return '\\r';
        case '\b': return '\\b';
        case '\t': return '\\t';
        case '\x1a': return '\\Z';
        default: return '\\' + s;
      }
    });
  }
  return (prependN ? "N'" : "'") + val + "'";
};

--- a/lib/sql-string.js
+++ b/lib/sql-string.js
@@ -34,7 +34,7 @@
   }

   if (Array.isArray(val)) {
-    var escape = _.partialRight(SqlString.escape, timeZone, dialect);
+    var escape = _.partial(SqlString.escape, _, timeZone, dialect, format);
     if (dialect === 'postgres' && !format) {
       return dataTypes.ARRAY.prototype.stringify(val, {escape: escape});
     }


Matrix-appservice-bridge is the bridging service for the Matrix communication program's application services. In versions 2.6.0 and earlier, if a bridge has room upgrade handling turned on in the configuration (the `roomUpgradeOpts` key when instantiating a new `Bridge` instance.), any `m.room.tombstone` event it encounters will be used to unbridge the current room and bridge into the target room. However, the target room `m.room.create` event is not checked to verify if the `predecessor` field contains the previous room. This means that any malicious admin of a bridged room can repoint the traffic to a different room without the new room being aware. Versions 2.6.1 and greater are patched. As a workaround, disabling the automatic room upgrade handling can be done by removing the `roomUpgradeOpts` key from the `Bridge` class options.

Missing Authentication for Critical Function

The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.

    private async onJoinedNewRoom(oldRoomId: string, newRoomId: string) {
        log.debug(`Joined ${newRoomId}`);
        const intent = this.bridge.getIntent();
        const asBot = this.bridge.getBot();
        if (this.opts.migrateStoreEntries) {
            const success = await this.migrateStoreEntries(oldRoomId, newRoomId);
            if (!success) {
                log.error("Failed to migrate room entries. Not continuing with migration.");
                return false;
            }
        }

        log.debug(`Migrated entries from ${oldRoomId} to ${newRoomId} successfully.`);
        if (this.opts.onRoomMigrated) {
            // This may or may not be a promise, so await it.
            await this.opts.onRoomMigrated(oldRoomId, newRoomId);
        }

        if (!this.opts.migrateGhosts) {
            return false;
        }
        try {
            const members = await asBot.getJoinedMembers(oldRoomId);
            const userIds = Object.keys(members).filter((u) => asBot.isRemoteUser(u));
            log.debug(`Migrating ${userIds.length} ghosts`);
            for (const userId of userIds) {
                const i = this.bridge.getIntent(userId);
                await i.leave(oldRoomId);
                await i.join(newRoomId);
            }
            intent.leave(oldRoomId);
        }
        catch (ex) {
            log.warn("Failed to migrate ghosts", ex);
            return false;
        }
        return true;
    }

    private async onJoinedNewRoom(oldRoomId: string, newRoomId: string) {
        log.debug(`Joined ${newRoomId}`);
        const intent = this.bridge.getIntent();
        const { predecessor } = await intent.getStateEvent(newRoomId, 'm.room.create');
        if (predecessor.room_id !== oldRoomId) {
            log.error(
    `Room doesn't have a matching predecessor (expected: ${oldRoomId}, got: ${predecessor.room_id}), not bridging.`
            );
            return false;
        }
        const asBot = this.bridge.getBot();
        if (this.opts.migrateStoreEntries) {
            const success = await this.migrateStoreEntries(oldRoomId, newRoomId);
            if (!success) {
                log.error("Failed to migrate room entries. Not continuing with migration.");
                return false;
            }
        }

        log.debug(`Migrated entries from ${oldRoomId} to ${newRoomId} successfully.`);
        if (this.opts.onRoomMigrated) {
            // This may or may not be a promise, so await it.
            await this.opts.onRoomMigrated(oldRoomId, newRoomId);
        }

        if (!this.opts.migrateGhosts) {
            return false;
        }
        try {
            const members = await asBot.getJoinedMembers(oldRoomId);
            const userIds = Object.keys(members).filter((u) => asBot.isRemoteUser(u));
            log.debug(`Migrating ${userIds.length} ghosts`);
            for (const userId of userIds) {
                const i = this.bridge.getIntent(userId);
                await i.leave(oldRoomId);
                await i.join(newRoomId);
            }
            intent.leave(oldRoomId);
        }
        catch (ex) {
            log.warn("Failed to migrate ghosts", ex);
            return false;
        }
        return true;
    }

--- a/src/components/room-upgrade-handler.ts
+++ b/src/components/room-upgrade-handler.ts
@@ -1,6 +1,13 @@
     private async onJoinedNewRoom(oldRoomId: string, newRoomId: string) {
         log.debug(`Joined ${newRoomId}`);
         const intent = this.bridge.getIntent();
+        const { predecessor } = await intent.getStateEvent(newRoomId, 'm.room.create');
+        if (predecessor.room_id !== oldRoomId) {
+            log.error(
+    `Room doesn't have a matching predecessor (expected: ${oldRoomId}, got: ${predecessor.room_id}), not bridging.`
+            );
+            return false;
+        }
         const asBot = this.bridge.getBot();
         if (this.opts.migrateStoreEntries) {
             const success = await this.migrateStoreEntries(oldRoomId, newRoomId);