Mycodo is an environmental monitoring and regulation system. An exploit in versions prior to 8.12.7 allows anyone with access to endpoints to download files outside the intended directory. A patch has been applied and a release made. Users should upgrade to version 8.12.7. As a workaround, users may manually apply the changes from the fix commit.

def send_note_attachment(filename):
    """Return a file from the note attachment directory"""
    file_path = os.path.join(PATH_NOTE_ATTACHMENTS, filename)
    if file_path is not None:
        try:
            return send_file(file_path, as_attachment=True)
        except Exception:
            logger.exception("Send note attachment")

def camera_img_return_path(camera_unique_id, img_type, filename):
    """Return an image from stills or time-lapses"""
    camera = Camera.query.filter(Camera.unique_id == camera_unique_id).first()
    camera_path = assure_path_exists(
        os.path.join(PATH_CAMERAS, '{uid}'.format(uid=camera.unique_id)))
    if img_type == 'still':
        if camera.path_still:
            path = camera.path_still
        else:
            path = os.path.join(camera_path, img_type)
    elif img_type == 'timelapse':
        if camera.path_timelapse:
            path = camera.path_timelapse
        else:
            path = os.path.join(camera_path, img_type)
    else:
        return "Unknown Image Type"

    if os.path.isdir(path):
        files = (files for files in os.listdir(path)
                 if os.path.isfile(os.path.join(path, files)))
    else:
        files = []
    if filename in files:
        path_file = os.path.join(path, filename)
        return send_file(path_file, mimetype='image/jpeg')

    return "Image not found"

def send_note_attachment(filename):
    """Return a file from the note attachment directory"""
    file_path = os.path.join(PATH_NOTE_ATTACHMENTS, filename)
    if file_path is not None:
        try:
            if os.path.abspath(file_path).startswith(PATH_NOTE_ATTACHMENTS):
                return send_file(file_path, as_attachment=True)
        except Exception:
            logger.exception("Send note attachment")

def camera_img_return_path(camera_unique_id, img_type, filename):
    """Return an image from stills or time-lapses"""
    camera = Camera.query.filter(Camera.unique_id == camera_unique_id).first()
    camera_path = assure_path_exists(
        os.path.join(PATH_CAMERAS, '{uid}'.format(uid=camera.unique_id)))
    if img_type == 'still':
        if camera.path_still:
            path = camera.path_still
        else:
            path = os.path.join(camera_path, img_type)
    elif img_type == 'timelapse':
        if camera.path_timelapse:
            path = camera.path_timelapse
        else:
            path = os.path.join(camera_path, img_type)
    else:
        return "Unknown Image Type"

    if os.path.isdir(path):
        files = (files for files in os.listdir(path)
                 if os.path.isfile(os.path.join(path, files)))
    else:
        files = []
    if filename in files:
        path_file = os.path.join(path, filename)
        if os.path.abspath(path_file).startswith(path):
            return send_file(path_file, mimetype='image/jpeg')

    return "Image not found"

--- a/mycodo/mycodo_flask/routes_general.py
+++ b/mycodo/mycodo_flask/routes_general.py
@@ -3,6 +3,7 @@
     file_path = os.path.join(PATH_NOTE_ATTACHMENTS, filename)
     if file_path is not None:
         try:
-            return send_file(file_path, as_attachment=True)
+            if os.path.abspath(file_path).startswith(PATH_NOTE_ATTACHMENTS):
+                return send_file(file_path, as_attachment=True)
         except Exception:
             logger.exception("Send note attachment")

--- a/mycodo/mycodo_flask/routes_general.py
+++ b/mycodo/mycodo_flask/routes_general.py
@@ -23,6 +23,7 @@
         files = []
     if filename in files:
         path_file = os.path.join(path, filename)
-        return send_file(path_file, mimetype='image/jpeg')
+        if os.path.abspath(path_file).startswith(path):
+            return send_file(path_file, mimetype='image/jpeg')

     return "Image not found"


heroku-addonpool through 0.1.15 is vulnerable to Command Injection.

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 HerokuAddonPool(id, app, opt) {
  const unused = [];
  const supply = new Map();
  const removed = new Map();
  const pending = new Map();
  opt = opt||{};
  opt.config = opt.config||/\S*/g;
  opt.log = opt.log||false;

  const log = function(msg) {
    if(opt.log) console.log(`${id}.${msg}`);
  };

  const supplySetOne = function(cfg) {
    return new Promise((fres, frej) => {
      const w = cfg.split('=');
      if(w[0].search(opt.config)<0) return;
      const key = w[0].substring(0, w[0].length-4);
      const val = {'value': w[1].substring(1, w[1].length-1)};
      cp.exec(`~/heroku addons:info ${key} --app ${app}`, (err, stdout) => {
        if(err) return frej(err);
        for(var r of stdout.toString().match(/[^\r\n]+/g)) {
          var k = _camel(r.startsWith('=')? 'name' : r.split(':')[0]);
          val[k] = r.substring(r.match(/[\S\s]+(=|:)\s+/g)[0].length);
        }
        supply.set(key, val);
        fres(key);
      });
    });
  };

  const supplySet = function() {
    return new Promise((fres, frej) => {
      cp.exec(`~/heroku config -s --app ${app}`, (err, stdout) => {
        if(err) return frej(err);
        var pro = Promise.resolve();
        for(var cfg of stdout.toString().match(/[^\r\n]+/g)||[])
          ((val) => pro = pro.then(() => supplySetOne(val)))(cfg);
        pro.then(() => fres(supply));
      });
    });
  };

  const setup = function() {
    log(`setup()`);
    return supplySet().then((ans) => {
      for(var key of supply.keys()) {
        log(`setup:addUnused(${key})`);
        unused.push(key);
      }
      return ans;
    });
  };

  const remove = function(ref) {
    return new Promise((fres) => {
      if(unused.length===0) {
        log(`remove:addPending(${ref})`);
        return pending.set(ref, fres);
      }
      const key = unused.shift();
      removed.set(ref, key);
      log(`remove:getUnused(${ref}, ${key})`);
      fres(supply.get(key));
    });
  };

  const supplyReset = function(key) {
    log(`supplyReset(${key})`);
    const plan = supply.get(key).plan;
    return new Promise((fres, frej) => cp.exec(
      `~/heroku addons:destroy ${key} -a ${app} --confirm ${app} >/dev/null && `+
      `~/heroku addons:create ${plan} --as ${key} -a ${app} >/dev/null && `+
      `~/heroku config -s -a ${app} | grep ^${key}`,
      (err, stdout) => {
        const r = stdout.toString();
        fres(supply.get(key).value = r.substring(r.indexOf('=')+2, r.length-2));
      }
    ));
  };

  const pendingRemove = function() {
    if(!unused.length || !pending.size) return;
    const ref = pending.keys().next().value;
    const fres = pending.get(ref);
    pending.delete(ref);
    const key = unused.shift();
    removed.set(ref, key);
    log(`pendingRemove:getUnused(${ref}, ${key})`);
    fres(supply.get(key));
    return ref;
  };

  const add = function(ref) {
    if(pending.has(ref)) {
      log(`add:removePending(${ref})`);
      pending.delete(ref);
    }
    if(removed.has(ref)) {
      const key = removed.get(ref);
      removed.delete(ref);
      log(`add:addUnused(${ref}, ${key})`);
      return supplyReset(key).then(() => {
        unused.push(key);
        pendingRemove();
        return ref;
      });
    }
    return Promise.resolve(ref);
  };

  return {add, remove, setup};
};

module.exports = function HerokuAddonPool(id, app, opt) {
  const unused = [];
  const supply = new Map();
  const removed = new Map();
  const pending = new Map();
  opt = opt||{};
  opt.config = opt.config||/\S*/g;
  opt.log = opt.log||false;
  if(!RAPP.test(app)) throw new Error('Bad app name');

  const log = function(msg) {
    if(opt.log) console.log(`${id}.${msg}`);
  };

  const supplySetOne = function(cfg) {
    return new Promise((fres, frej) => {
      const w = cfg.split('=');
      if(w[0].search(opt.config)<0) return;
      const key = w[0].substring(0, w[0].length-4);
      const val = {'value': w[1].substring(1, w[1].length-1)};
      cp.exec(`~/heroku addons:info ${key} --app ${app}`, (err, stdout) => {
        if(err) return frej(err);
        for(var r of stdout.toString().match(/[^\r\n]+/g)) {
          var k = _camel(r.startsWith('=')? 'name' : r.split(':')[0]);
          val[k] = r.substring(r.match(/[\S\s]+(=|:)\s+/g)[0].length);
        }
        supply.set(key, val);
        fres(key);
      });
    });
  };

  const supplySet = function() {
    return new Promise((fres, frej) => {
      cp.exec(`~/heroku config -s --app ${app}`, (err, stdout) => {
        if(err) return frej(err);
        var pro = Promise.resolve();
        for(var cfg of stdout.toString().match(/[^\r\n]+/g)||[])
          ((val) => pro = pro.then(() => supplySetOne(val)))(cfg);
        pro.then(() => fres(supply));
      });
    });
  };

  const setup = function() {
    log(`setup()`);
    return supplySet().then((ans) => {
      for(var key of supply.keys()) {
        log(`setup:addUnused(${key})`);
        unused.push(key);
      }
      return ans;
    });
  };

  const remove = function(ref) {
    return new Promise((fres) => {
      if(unused.length===0) {
        log(`remove:addPending(${ref})`);
        return pending.set(ref, fres);
      }
      const key = unused.shift();
      removed.set(ref, key);
      log(`remove:getUnused(${ref}, ${key})`);
      fres(supply.get(key));
    });
  };

  const supplyReset = function(key) {
    log(`supplyReset(${key})`);
    const plan = supply.get(key).plan;
    return new Promise((fres, frej) => cp.exec(
      `~/heroku addons:destroy ${key} -a ${app} --confirm ${app} >/dev/null && `+
      `~/heroku addons:create ${plan} --as ${key} -a ${app} >/dev/null && `+
      `~/heroku config -s -a ${app} | grep ^${key}`,
      (err, stdout) => {
        const r = stdout.toString();
        fres(supply.get(key).value = r.substring(r.indexOf('=')+2, r.length-2));
      }
    ));
  };

  const pendingRemove = function() {
    if(!unused.length || !pending.size) return;
    const ref = pending.keys().next().value;
    const fres = pending.get(ref);
    pending.delete(ref);
    const key = unused.shift();
    removed.set(ref, key);
    log(`pendingRemove:getUnused(${ref}, ${key})`);
    fres(supply.get(key));
    return ref;
  };

  const add = function(ref) {
    if(pending.has(ref)) {
      log(`add:removePending(${ref})`);
      pending.delete(ref);
    }
    if(removed.has(ref)) {
      const key = removed.get(ref);
      removed.delete(ref);
      log(`add:addUnused(${ref}, ${key})`);
      return supplyReset(key).then(() => {
        unused.push(key);
        pendingRemove();
        return ref;
      });
    }
    return Promise.resolve(ref);
  };

  return {add, remove, setup};
};

--- a/index.js
+++ b/index.js
@@ -6,6 +6,7 @@
   opt = opt||{};
   opt.config = opt.config||/\S*/g;
   opt.log = opt.log||false;
+  if(!RAPP.test(app)) throw new Error('Bad app name');

   const log = function(msg) {
     if(opt.log) console.log(`${id}.${msg}`);


A vulnerability was found in kitsada8621 Digital Library Management System 1.0. It has been classified as problematic. Affected is the function JwtRefreshAuth of the file middleware/jwt_refresh_token_middleware.go. The manipulation of the argument Authorization leads to improper output neutralization for logs. It is possible to launch the attack remotely. The name of the patch is 81b3336b4c9240f0bf50c13cb8375cf860d945f1. It is recommended to apply a patch to fix this issue.

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.

func InitBorrowRoute(r *gin.RouterGroup) {

	borrowController := controllers.NewBorrowController()
	borrowRoute := r.Group("borrow")
	borrowRoute.Use(middleware.JwtAuth())

	borrowRoute.POST("/create", middleware.Role([]string{"user"}), borrowController.BorrowBook)
	borrowRoute.PUT("/update/:id", middleware.Role([]string{"user"}), borrowController.UpdateBorrow)
	borrowRoute.PUT("/cancel/:id", middleware.Role([]string{"user"}), borrowController.CancelBorrowBook)
	borrowRoute.GET("/history", middleware.Role([]string{"user"}), borrowController.BorrowingHistory)

	borrowRoute.Use(middleware.Role([]string{"admin"}))
	borrowRoute.GET("/all", borrowController.GetBorrows)
	borrowRoute.GET("/:id", borrowController.BorrowDetails)
	borrowRoute.DELETE("/delete/:id", borrowController.DeleteBorrow)
	borrowRoute.PUT("/approve/:id", borrowController.ApproveBookBorrowing)

}

func (s *BorrowServiceImpl) UpdateBorrow(data dtos.BorrowDto, borrow models.Borrow) (int, error) {

	if borrow.BorrowStatus != 1 {
		return http.StatusOK, fmt.Errorf("Unable to edit")
	}

	book, err := s.bookRepository.FindOne(bson.M{"_id": borrow.BookId})
	if err != nil {
		return http.StatusInternalServerError, err
	}

	if book.BookStatus != 0 {
		return http.StatusOK, fmt.Errorf("This book has been borrowed")
	}

	returnDate, err := utils.StringToDate(data.ReturnDate)
	if err != nil {
		return http.StatusBadRequest, err
	}

	if _, err := s.borrowRepository.Update(bson.M{
		"$set": bson.M{
			"bookId":     book.ID,
			"returnDate": returnDate,
		},
	}, bson.M{"_id": borrow.ID}); err != nil {
		return http.StatusInternalServerError, err
	}

	return http.StatusOK, nil
}

func InitBorrowRoute(r *gin.RouterGroup) {

	borrowController := controllers.NewBorrowController()
	borrowRoute := r.Group("borrow")
	borrowRoute.Use(middleware.JwtAuth())

	borrowRoute.POST("/create", middleware.Role([]string{"user"}), borrowController.BorrowBook)
	borrowRoute.PUT("/update/:id", middleware.Role([]string{"user"}), borrowController.UpdateBorrow)
	borrowRoute.PUT("/cancel/:id", middleware.Role([]string{"user"}), borrowController.CancelBorrowBook)
	borrowRoute.GET("/history", middleware.Role([]string{"user"}), borrowController.BorrowingHistory)
	borrowRoute.GET("/:id", middleware.Role([]string{"user", "admin"}), borrowController.BorrowDetails)

	borrowRoute.Use(middleware.Role([]string{"admin"}))
	borrowRoute.GET("/all", borrowController.GetBorrows)
	borrowRoute.DELETE("/delete/:id", borrowController.DeleteBorrow)
	borrowRoute.PUT("/approve/:id", borrowController.ApproveBookBorrowing)

}

func (s *BorrowServiceImpl) UpdateBorrow(data dtos.BorrowDto, borrow models.Borrow) (int, error) {

	if borrow.BorrowStatus != 1 {
		return http.StatusOK, fmt.Errorf("Unable to edit")
	}

	bookId, err := primitive.ObjectIDFromHex(data.BookId)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	book, err := s.bookRepository.FindOne(bson.M{"_id": bookId})
	if err != nil {
		return http.StatusInternalServerError, err
	}

	if book.BookStatus != 0 {
		return http.StatusOK, fmt.Errorf("This book has been borrowed")
	}

	returnDate, err := utils.StringToDate(data.ReturnDate)
	if err != nil {
		return http.StatusBadRequest, err
	}

	if _, err := s.borrowRepository.Update(bson.M{
		"$set": bson.M{
			"bookId":     book.ID,
			"returnDate": returnDate,
		},
	}, bson.M{"_id": borrow.ID}); err != nil {
		return http.StatusInternalServerError, err
	}

	return http.StatusOK, nil
}

--- a/routes/borrow_route.go
+++ b/routes/borrow_route.go
@@ -8,10 +8,10 @@
 	borrowRoute.PUT("/update/:id", middleware.Role([]string{"user"}), borrowController.UpdateBorrow)
 	borrowRoute.PUT("/cancel/:id", middleware.Role([]string{"user"}), borrowController.CancelBorrowBook)
 	borrowRoute.GET("/history", middleware.Role([]string{"user"}), borrowController.BorrowingHistory)
+	borrowRoute.GET("/:id", middleware.Role([]string{"user", "admin"}), borrowController.BorrowDetails)

 	borrowRoute.Use(middleware.Role([]string{"admin"}))
 	borrowRoute.GET("/all", borrowController.GetBorrows)
-	borrowRoute.GET("/:id", borrowController.BorrowDetails)
 	borrowRoute.DELETE("/delete/:id", borrowController.DeleteBorrow)
 	borrowRoute.PUT("/approve/:id", borrowController.ApproveBookBorrowing)

--- a/services/borrow_service.go
+++ b/services/borrow_service.go
@@ -4,7 +4,12 @@
 		return http.StatusOK, fmt.Errorf("Unable to edit")
 	}

-	book, err := s.bookRepository.FindOne(bson.M{"_id": borrow.BookId})
+	bookId, err := primitive.ObjectIDFromHex(data.BookId)
+	if err != nil {
+		return http.StatusInternalServerError, err
+	}
+
+	book, err := s.bookRepository.FindOne(bson.M{"_id": bookId})
 	if err != nil {
 		return http.StatusInternalServerError, err
 	}


A Missing Authorization vulnerability in of SUSE Rancher allows authenticated user to create an unauthorized shell pod and kubectl access in the local cluster This issue affects: SUSE Rancher Rancher versions prior to 2.5.17; Rancher versions prior to 2.6.10; Rancher versions prior to 2.7.1.

Missing Authorization

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

func NewProxyMiddleware(sar v1.AuthorizationV1Interface,
	dialerFactory ClusterDialerFactory,
	clusters v3.ClusterCache,
	localSupport bool,
	localCluster http.Handler) (func(http.Handler) http.Handler, error) {
	cfg := authorizerfactory.DelegatingAuthorizerConfig{
		SubjectAccessReviewClient: sar,
		AllowCacheTTL:             time.Second * time.Duration(settings.AuthorizationCacheTTLSeconds.GetInt()),
		DenyCacheTTL:              time.Second * time.Duration(settings.AuthorizationDenyCacheTTLSeconds.GetInt()),
		WebhookRetryBackoff:       &auth.WebhookBackoff,
	}

	authorizer, err := cfg.New()
	if err != nil {
		return nil, err
	}

	proxyHandler := NewProxyHandler(authorizer, dialerFactory, clusters)

	mux := gmux.NewRouter()
	mux.UseEncodedPath()
	mux.PathPrefix("/api").HandlerFunc(proxyHandler.authLocalCluster(mux))
	mux.Path("/v1/management.cattle.io.clusters/{clusterID}").Queries("link", "shell").HandlerFunc(routeToShellProxy("link", "shell", localSupport, localCluster, mux, proxyHandler))
	mux.Path("/v1/management.cattle.io.clusters/{clusterID}").Queries("action", "apply").HandlerFunc(routeToShellProxy("action", "apply", localSupport, localCluster, mux, proxyHandler))
	mux.Path("/v3/clusters/{clusterID}").Queries("shell", "true").HandlerFunc(routeToShellProxy("link", "shell", localSupport, localCluster, mux, proxyHandler))
	mux.Path("/{prefix:k8s/clusters/[^/]+}{suffix:/v1.*}").MatcherFunc(proxyHandler.MatchNonLegacy("/k8s/clusters/")).Handler(proxyHandler)

	return func(handler http.Handler) http.Handler {
		return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
			mux.NotFoundHandler = handler
			mux.ServeHTTP(rw, req)
		})
	}, nil
}

func (h *Handler) authLocalCluster(router *gmux.Router) func(rw http.ResponseWriter, r *http.Request) {
	return func(rw http.ResponseWriter, req *http.Request) {
		authed := h.userCanAccessCluster(req, "local")
		if !authed {
			if req.Context().Value(auth.CattleAuthFailed) != "true" {
				rw.WriteHeader(http.StatusUnauthorized)
				return
			}
		}
		router.NotFoundHandler.ServeHTTP(rw, req)
	}
}

func NewProxyMiddleware(sar v1.AuthorizationV1Interface,
	dialerFactory ClusterDialerFactory,
	clusters v3.ClusterCache,
	localSupport bool,
	localCluster http.Handler) (func(http.Handler) http.Handler, error) {
	cfg := authorizerfactory.DelegatingAuthorizerConfig{
		SubjectAccessReviewClient: sar,
		AllowCacheTTL:             time.Second * time.Duration(settings.AuthorizationCacheTTLSeconds.GetInt()),
		DenyCacheTTL:              time.Second * time.Duration(settings.AuthorizationDenyCacheTTLSeconds.GetInt()),
		WebhookRetryBackoff:       &auth.WebhookBackoff,
	}

	authorizer, err := cfg.New()
	if err != nil {
		return nil, err
	}

	proxyHandler := NewProxyHandler(authorizer, dialerFactory, clusters)

	mux := gmux.NewRouter()
	mux.UseEncodedPath()
	mux.PathPrefix("/api").MatcherFunc(proxyHandler.matchManagementCRDs()).HandlerFunc(proxyHandler.authLocalCluster(mux))
	mux.Path("/v1/management.cattle.io.clusters/{clusterID}").Queries("link", "shell").HandlerFunc(routeToShellProxy("link", "shell", localSupport, localCluster, mux, proxyHandler))
	mux.Path("/v1/management.cattle.io.clusters/{clusterID}").Queries("action", "apply").HandlerFunc(routeToShellProxy("action", "apply", localSupport, localCluster, mux, proxyHandler))
	mux.Path("/v3/clusters/{clusterID}").Queries("shell", "true").HandlerFunc(routeToShellProxy("link", "shell", localSupport, localCluster, mux, proxyHandler))
	mux.Path("/{prefix:k8s/clusters/[^/]+}{suffix:/v1.*}").MatcherFunc(proxyHandler.MatchNonLegacy("/k8s/clusters/")).Handler(proxyHandler)

	return func(handler http.Handler) http.Handler {
		return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
			mux.NotFoundHandler = handler
			mux.ServeHTTP(rw, req)
		})
	}, nil
}

func (h *Handler) authLocalCluster(router *gmux.Router) func(rw http.ResponseWriter, r *http.Request) {
	return func(rw http.ResponseWriter, req *http.Request) {
		authed := h.userCanAccessCluster(req, "local")
		if !authed {
			rw.WriteHeader(http.StatusForbidden)
			return
		}
		router.NotFoundHandler.ServeHTTP(rw, req)
	}
}

func (h *Handler) matchManagementCRDs() gmux.MatcherFunc {
	return func(req *http.Request, match *gmux.RouteMatch) bool {
		splitPath := strings.Split(req.URL.Path, "/")
		if len(splitPath) < 3 {
			return false
		}
		apiGroup := splitPath[2]
		return apiGroup == managementv3.GroupName
	}
}

--- a/pkg/api/steve/proxy/proxy.go
+++ b/pkg/api/steve/proxy/proxy.go
@@ -19,7 +19,7 @@

 	mux := gmux.NewRouter()
 	mux.UseEncodedPath()
-	mux.PathPrefix("/api").HandlerFunc(proxyHandler.authLocalCluster(mux))
+	mux.PathPrefix("/api").MatcherFunc(proxyHandler.matchManagementCRDs()).HandlerFunc(proxyHandler.authLocalCluster(mux))
 	mux.Path("/v1/management.cattle.io.clusters/{clusterID}").Queries("link", "shell").HandlerFunc(routeToShellProxy("link", "shell", localSupport, localCluster, mux, proxyHandler))
 	mux.Path("/v1/management.cattle.io.clusters/{clusterID}").Queries("action", "apply").HandlerFunc(routeToShellProxy("action", "apply", localSupport, localCluster, mux, proxyHandler))
 	mux.Path("/v3/clusters/{clusterID}").Queries("shell", "true").HandlerFunc(routeToShellProxy("link", "shell", localSupport, localCluster, mux, proxyHandler))

--- a/pkg/api/steve/proxy/proxy.go
+++ b/pkg/api/steve/proxy/proxy.go
@@ -2,10 +2,8 @@
 	return func(rw http.ResponseWriter, req *http.Request) {
 		authed := h.userCanAccessCluster(req, "local")
 		if !authed {
-			if req.Context().Value(auth.CattleAuthFailed) != "true" {
-				rw.WriteHeader(http.StatusUnauthorized)
-				return
-			}
+			rw.WriteHeader(http.StatusForbidden)
+			return
 		}
 		router.NotFoundHandler.ServeHTTP(rw, req)
 	}

--- /dev/null
+++ b/pkg/api/steve/proxy/proxy.go
@@ -0,0 +1,10 @@
+func (h *Handler) matchManagementCRDs() gmux.MatcherFunc {
+	return func(req *http.Request, match *gmux.RouteMatch) bool {
+		splitPath := strings.Split(req.URL.Path, "/")
+		if len(splitPath) < 3 {
+			return false
+		}
+		apiGroup := splitPath[2]
+		return apiGroup == managementv3.GroupName
+	}
+}


Open Redirect in GitHub repository go-gitea/gitea prior to 1.19.4.

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 (ctx *Context) RedirectToFirst(location ...string) {
	for _, loc := range location {
		if len(loc) == 0 {
			continue
		}

		// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
		// Therefore we should ignore these redirect locations to prevent open redirects
		if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
			continue
		}

		u, err := url.Parse(loc)
		if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
			continue
		}

		ctx.Redirect(loc)
		return
	}

	ctx.Redirect(setting.AppSubURL + "/")
}

func (ctx *Context) RedirectToFirst(location ...string) {
	for _, loc := range location {
		if len(loc) == 0 {
			continue
		}

		// Unfortunately browsers consider a redirect Location with preceding "//", "\\" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
		// Therefore we should ignore these redirect locations to prevent open redirects
		if len(loc) > 1 && (loc[0] == '/' || loc[0] == '\\') && (loc[1] == '/' || loc[1] == '\\') {
			continue
		}

		u, err := url.Parse(loc)
		if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
			continue
		}

		ctx.Redirect(loc)
		return
	}

	ctx.Redirect(setting.AppSubURL + "/")
}

--- a/modules/context/context_response.go
+++ b/modules/context/context_response.go
@@ -4,9 +4,9 @@
 			continue
 		}

-		// Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
+		// Unfortunately browsers consider a redirect Location with preceding "//", "\\" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
 		// Therefore we should ignore these redirect locations to prevent open redirects
-		if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
+		if len(loc) > 1 && (loc[0] == '/' || loc[0] == '\\') && (loc[1] == '/' || loc[1] == '\\') {
 			continue
 		}


In BigBlueButton before 2.2.7, lockSettingsProps.disablePrivateChat does not apply to already opened chats. This occurs in bigbluebutton-html5/imports/ui/components/chat/service.js.

Exposure of Resource to Wrong Sphere

The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.

const isChatLocked = (receiverID) => {
  const isPublic = receiverID === PUBLIC_CHAT_ID;

  const meeting = Meetings.findOne({ meetingId: Auth.meetingID },
    { fields: { 'lockSettingsProps.disablePublicChat': 1 } });
  const user = Users.findOne({ meetingId: Auth.meetingID, userId: Auth.userID },
    { fields: { locked: 1, role: 1 } });
  const receiver = Users.findOne({ meetingId: Auth.meetingID, userId: receiverID },
    { fields: { role: 1 } });
  const isReceiverModerator = receiver && receiver.role === ROLE_MODERATOR;

  if (meeting.lockSettingsProps !== undefined) {
    if (user.locked && user.role !== ROLE_MODERATOR) {
      if (isPublic) {
        return meeting.lockSettingsProps.disablePublicChat;
      }
      return !isReceiverModerator
        && meeting.lockSettingsProps.disablePrivateChat;
    }
  }

  return false;
};

const isChatLocked = (receiverID) => {
  const isPublic = receiverID === PUBLIC_CHAT_ID;
  const meeting = Meetings.findOne({ meetingId: Auth.meetingID },
    { fields: { 'lockSettingsProps.disablePublicChat': 1, 'lockSettingsProps.disablePrivateChat': 1 } });
  const user = Users.findOne({ meetingId: Auth.meetingID, userId: Auth.userID },
    { fields: { locked: 1, role: 1 } });
  const receiver = Users.findOne({ meetingId: Auth.meetingID, userId: receiverID },
    { fields: { role: 1 } });
  const isReceiverModerator = receiver && receiver.role === ROLE_MODERATOR;

  if (meeting.lockSettingsProps !== undefined) {
    if (user.locked && user.role !== ROLE_MODERATOR) {
      if (isPublic) {
        return meeting.lockSettingsProps.disablePublicChat;
      }
      return !isReceiverModerator
        && meeting.lockSettingsProps.disablePrivateChat;
    }
  }

  return false;
};

--- a/bigbluebutton-html5/imports/ui/components/chat/service.js
+++ b/bigbluebutton-html5/imports/ui/components/chat/service.js
@@ -1,8 +1,7 @@
 const isChatLocked = (receiverID) => {
   const isPublic = receiverID === PUBLIC_CHAT_ID;
-
   const meeting = Meetings.findOne({ meetingId: Auth.meetingID },
-    { fields: { 'lockSettingsProps.disablePublicChat': 1 } });
+    { fields: { 'lockSettingsProps.disablePublicChat': 1, 'lockSettingsProps.disablePrivateChat': 1 } });
   const user = Users.findOne({ meetingId: Auth.meetingID, userId: Auth.userID },
     { fields: { locked: 1, role: 1 } });
   const receiver = Users.findOne({ meetingId: Auth.meetingID, userId: receiverID },


The dag-pb codec can panic when decoding invalid blocks.

Improper Restriction of Operations within the Bounds of a Memory Buffer

The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.

func DecodeBytes(na ipld.NodeAssembler, src []byte) error {
	remaining := src

	ma, err := na.BeginMap(2)
	if err != nil {
		return err
	}
	var links ipld.ListAssembler

	haveData := false
	haveLinks := false
	for {
		if len(remaining) == 0 {
			break
		}

		fieldNum, wireType, n := protowire.ConsumeTag(remaining)
		if n < 0 {
			return protowire.ParseError(n)
		}
		remaining = remaining[n:]

		if wireType != 2 {
			return fmt.Errorf("protobuf: (PBNode) invalid wireType, expected 2, got %d", wireType)
		}

		// Note that we allow Data and Links to come in either order,
		// since the spec defines that decoding "should" accept either form.
		// This is for backwards compatibility with older IPFS data.

		switch fieldNum {
		case 1:
			if haveData {
				return fmt.Errorf("protobuf: (PBNode) duplicate Data section")
			}

			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
			}
			remaining = remaining[n:]

			if links != nil {
				// Links came before Data.
				// Finish them before we start Data.
				if err := links.Finish(); err != nil {
					return err
				}
				links = nil
			}

			if err := ma.AssembleKey().AssignString("Data"); err != nil {
				return err
			}
			if err := ma.AssembleValue().AssignBytes(chunk); err != nil {
				return err
			}
			haveData = true

		case 2:
			bytesLen, n := protowire.ConsumeVarint(remaining)
			if n < 0 {
				return protowire.ParseError(n)
			}
			remaining = remaining[n:]

			if links == nil {
				if haveLinks {
					return fmt.Errorf("protobuf: (PBNode) duplicate Links section")
				}

				// The repeated "Links" part begins.
				if err := ma.AssembleKey().AssignString("Links"); err != nil {
					return err
				}
				links, err = ma.AssembleValue().BeginList(0)
				if err != nil {
					return err
				}
			}

			curLink, err := links.AssembleValue().BeginMap(3)
			if err != nil {
				return err
			}
			if err := unmarshalLink(remaining[:bytesLen], curLink); err != nil {
				return err
			}
			remaining = remaining[bytesLen:]
			if err := curLink.Finish(); err != nil {
				return err
			}
			haveLinks = true

		default:
			return fmt.Errorf("protobuf: (PBNode) invalid fieldNumber, expected 1 or 2, got %d", fieldNum)
		}
	}

	if links != nil {
		// We had some links at the end, so finish them.
		if err := links.Finish(); err != nil {
			return err
		}

	} else if !haveLinks {
		// We didn't have any links.
		// Since we always want a Links field, add one here.
		if err := ma.AssembleKey().AssignString("Links"); err != nil {
			return err
		}
		links, err := ma.AssembleValue().BeginList(0)
		if err != nil {
			return err
		}
		if err := links.Finish(); err != nil {
			return err
		}
	}
	return ma.Finish()
}

func DecodeBytes(na ipld.NodeAssembler, src []byte) error {
	remaining := src

	ma, err := na.BeginMap(2)
	if err != nil {
		return err
	}
	var links ipld.ListAssembler

	haveData := false
	haveLinks := false
	for {
		if len(remaining) == 0 {
			break
		}

		fieldNum, wireType, n := protowire.ConsumeTag(remaining)
		if n < 0 {
			return protowire.ParseError(n)
		}
		remaining = remaining[n:]

		if wireType != 2 {
			return fmt.Errorf("protobuf: (PBNode) invalid wireType, expected 2, got %d", wireType)
		}

		// Note that we allow Data and Links to come in either order,
		// since the spec defines that decoding "should" accept either form.
		// This is for backwards compatibility with older IPFS data.

		switch fieldNum {
		case 1:
			if haveData {
				return fmt.Errorf("protobuf: (PBNode) duplicate Data section")
			}

			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
			}
			remaining = remaining[n:]

			if links != nil {
				// Links came before Data.
				// Finish them before we start Data.
				if err := links.Finish(); err != nil {
					return err
				}
				links = nil
			}

			if err := ma.AssembleKey().AssignString("Data"); err != nil {
				return err
			}
			if err := ma.AssembleValue().AssignBytes(chunk); err != nil {
				return err
			}
			haveData = true

		case 2:
			chunk, n := protowire.ConsumeBytes(remaining)
			if n < 0 {
				return protowire.ParseError(n)
			}
			remaining = remaining[n:]

			if links == nil {
				if haveLinks {
					return fmt.Errorf("protobuf: (PBNode) duplicate Links section")
				}

				// The repeated "Links" part begins.
				if err := ma.AssembleKey().AssignString("Links"); err != nil {
					return err
				}
				links, err = ma.AssembleValue().BeginList(0)
				if err != nil {
					return err
				}
			}

			curLink, err := links.AssembleValue().BeginMap(3)
			if err != nil {
				return err
			}
			if err := unmarshalLink(chunk, curLink); err != nil {
				return err
			}
			if err := curLink.Finish(); err != nil {
				return err
			}
			haveLinks = true

		default:
			return fmt.Errorf("protobuf: (PBNode) invalid fieldNumber, expected 1 or 2, got %d", fieldNum)
		}
	}

	if links != nil {
		// We had some links at the end, so finish them.
		if err := links.Finish(); err != nil {
			return err
		}

	} else if !haveLinks {
		// We didn't have any links.
		// Since we always want a Links field, add one here.
		if err := ma.AssembleKey().AssignString("Links"); err != nil {
			return err
		}
		links, err := ma.AssembleValue().BeginList(0)
		if err != nil {
			return err
		}
		if err := links.Finish(); err != nil {
			return err
		}
	}
	return ma.Finish()
}

--- a/unmarshal.go
+++ b/unmarshal.go
@@ -58,7 +58,7 @@
 			haveData = true

 		case 2:
-			bytesLen, n := protowire.ConsumeVarint(remaining)
+			chunk, n := protowire.ConsumeBytes(remaining)
 			if n < 0 {
 				return protowire.ParseError(n)
 			}
@@ -83,10 +83,9 @@
 			if err != nil {
 				return err
 			}
-			if err := unmarshalLink(remaining[:bytesLen], curLink); err != nil {
+			if err := unmarshalLink(chunk, curLink); err != nil {
 				return err
 			}
-			remaining = remaining[bytesLen:]
 			if err := curLink.Finish(); err != nil {
 				return err
 			}


Authorization Bypass Through User-Controlled Key in NPM urijs prior to 1.19.8.

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

  URI.parse = function(string, parts) {
    var pos;
    if (!parts) {
      parts = {
        preventInvalidHostname: URI.preventInvalidHostname
      };
    }
    // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]

    // extract fragment
    pos = string.indexOf('#');
    if (pos > -1) {
      // escaping?
      parts.fragment = string.substring(pos + 1) || null;
      string = string.substring(0, pos);
    }

    // extract query
    pos = string.indexOf('?');
    if (pos > -1) {
      // escaping?
      parts.query = string.substring(pos + 1) || null;
      string = string.substring(0, pos);
    }

    // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/, '$1://');

    // extract protocol
    if (string.substring(0, 2) === '//') {
      // relative-scheme
      parts.protocol = null;
      string = string.substring(2);
      // extract "user:pass@host:port"
      string = URI.parseAuthority(string, parts);
    } else {
      pos = string.indexOf(':');
      if (pos > -1) {
        parts.protocol = string.substring(0, pos) || null;
        if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
          // : may be within the path
          parts.protocol = undefined;
        } else if (string.substring(pos + 1, pos + 3).replace(/\\/g, '/') === '//') {
          string = string.substring(pos + 3);

          // extract "user:pass@host:port"
          string = URI.parseAuthority(string, parts);
        } else {
          string = string.substring(pos + 1);
          parts.urn = true;
        }
      }
    }

    // what's left must be the path
    parts.path = string;

    // and we're done
    return parts;
  };

  URI.parse = function(string, parts) {
    var pos;
    if (!parts) {
      parts = {
        preventInvalidHostname: URI.preventInvalidHostname
      };
    }
    // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]

    // extract fragment
    pos = string.indexOf('#');
    if (pos > -1) {
      // escaping?
      parts.fragment = string.substring(pos + 1) || null;
      string = string.substring(0, pos);
    }

    // extract query
    pos = string.indexOf('?');
    if (pos > -1) {
      // escaping?
      parts.query = string.substring(pos + 1) || null;
      string = string.substring(0, pos);
    }

    // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/i, '$1://');

    // extract protocol
    if (string.substring(0, 2) === '//') {
      // relative-scheme
      parts.protocol = null;
      string = string.substring(2);
      // extract "user:pass@host:port"
      string = URI.parseAuthority(string, parts);
    } else {
      pos = string.indexOf(':');
      if (pos > -1) {
        parts.protocol = string.substring(0, pos) || null;
        if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
          // : may be within the path
          parts.protocol = undefined;
        } else if (string.substring(pos + 1, pos + 3).replace(/\\/g, '/') === '//') {
          string = string.substring(pos + 3);

          // extract "user:pass@host:port"
          string = URI.parseAuthority(string, parts);
        } else {
          string = string.substring(pos + 1);
          parts.urn = true;
        }
      }
    }

    // what's left must be the path
    parts.path = string;

    // and we're done
    return parts;
  };

--- a/src/URI.js
+++ b/src/URI.js
@@ -24,7 +24,7 @@
     }

     // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
-    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/, '$1://');
+    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/i, '$1://');

     // extract protocol
     if (string.substring(0, 2) === '//') {


An error in the implementation of an autosubscribe feature in the check_stream_exists route of the Zulip group chat application server before 1.4.3 allowed an authenticated user to subscribe to a private stream that should have required an invitation from an existing member to join. The issue affects all previously released versions of the Zulip server.

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.

def stream_exists_backend(request, user_profile, stream_id, autosubscribe):
    # type: (HttpRequest, UserProfile, int, bool) -> HttpResponse
    try:
        stream = get_and_validate_stream_by_id(stream_id, user_profile.realm)
    except JsonableError:
        stream = None
    result = {"exists": bool(stream)}
    if stream is not None:
        recipient = get_recipient(Recipient.STREAM, stream.id)
        if autosubscribe:
            bulk_add_subscriptions([stream], [user_profile])
        result["subscribed"] = is_active_subscriber(
            user_profile=user_profile,
            recipient=recipient)

        return json_success(result) # results are ignored for HEAD requests
    return json_response(data=result, status=404)

def stream_exists_backend(request, user_profile, stream_id, autosubscribe):
    # type: (HttpRequest, UserProfile, int, bool) -> HttpResponse
    try:
        stream = get_and_validate_stream_by_id(stream_id, user_profile.realm)
    except JsonableError:
        stream = None
    result = {"exists": bool(stream)}
    if stream is not None:
        recipient = get_recipient(Recipient.STREAM, stream.id)
        if not stream.invite_only and autosubscribe:
            bulk_add_subscriptions([stream], [user_profile])
        result["subscribed"] = is_active_subscriber(
            user_profile=user_profile,
            recipient=recipient)

        return json_success(result) # results are ignored for HEAD requests
    return json_response(data=result, status=404)

--- a/zerver/views/streams.py
+++ b/zerver/views/streams.py
@@ -7,7 +7,7 @@
     result = {"exists": bool(stream)}
     if stream is not None:
         recipient = get_recipient(Recipient.STREAM, stream.id)
-        if autosubscribe:
+        if not stream.invite_only and autosubscribe:
             bulk_add_subscriptions([stream], [user_profile])
         result["subscribed"] = is_active_subscriber(
             user_profile=user_profile,


Copyparty is a portable file server. Versions prior to 1.8.2 are subject to a path traversal vulnerability detected in the `.cpr` subfolder. The Path Traversal attack technique allows an attacker access to files, directories, and commands that reside outside the web document root directory. This issue has been addressed in commit `043e3c7d` which has been included in release 1.8.2. Users are advised to upgrade. There are no known workarounds for this vulnerability.

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 handle_get(self) -> bool:
        if self.do_log:
            logmsg = "%-4s %s @%s" % (self.mode, self.req, self.uname)

            if "range" in self.headers:
                try:
                    rval = self.headers["range"].split("=", 1)[1]
                except:
                    rval = self.headers["range"]

                logmsg += " [\033[36m" + rval + "\033[0m]"

            self.log(logmsg)

        # "embedded" resources
        if self.vpath.startswith(".cpr"):
            if self.vpath.startswith(".cpr/ico/"):
                return self.tx_ico(self.vpath.split("/")[-1], exact=True)

            if self.vpath.startswith(".cpr/ssdp"):
                return self.conn.hsrv.ssdp.reply(self)

            if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
                if self.args.mpmc == ".":
                    raise Pebkac(404)

                loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
                h = {"Location": loc, "Cache-Control": "max-age=39"}
                self.reply(b"", 301, headers=h)
                return True

            static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
            return self.tx_file(static_path)

        if "cf_challenge" in self.uparam:
            self.reply(self.j2s("cf").encode("utf-8", "replace"))
            return True

        if not self.can_read and not self.can_write and not self.can_get:
            t = "@{} has no access to [{}]"
            self.log(t.format(self.uname, self.vpath))

            if "on403" in self.vn.flags:
                ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
                if ret == "true":
                    return True
                elif ret == "false":
                    return False
                elif ret == "allow":
                    self.log("plugin override; access permitted")
                    self.can_read = self.can_write = self.can_move = True
                    self.can_delete = self.can_get = self.can_upget = True
                    self.can_admin = True
                else:
                    return self.tx_404(True)
            else:
                if self.vpath:
                    return self.tx_404(True)

                self.uparam["h"] = ""

        if "tree" in self.uparam:
            return self.tx_tree()

        if "scan" in self.uparam:
            return self.scanvol()

        if self.args.getmod:
            if "delete" in self.uparam:
                return self.handle_rm([])

            if "move" in self.uparam:
                return self.handle_mv()

        if not self.vpath:
            if "reload" in self.uparam:
                return self.handle_reload()

            if "stack" in self.uparam:
                return self.tx_stack()

            if "ups" in self.uparam:
                return self.tx_ups()

            if "k304" in self.uparam:
                return self.set_k304()

            if "setck" in self.uparam:
                return self.setck()

            if "reset" in self.uparam:
                return self.set_cfg_reset()

            if "hc" in self.uparam:
                return self.tx_svcs()

        if "h" in self.uparam:
            return self.tx_mounts()

        # conditional redirect to single volumes
        if self.vpath == "" and not self.ouparam:
            nread = len(self.rvol)
            nwrite = len(self.wvol)
            if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1):
                if nread == 1:
                    vpath = self.rvol[0]
                else:
                    vpath = self.wvol[0]

                if self.vpath != vpath:
                    self.redirect(vpath, flavor="redirecting to", use302=True)
                    return True

        return self.tx_browser()

    def handle_get(self) -> bool:
        if self.do_log:
            logmsg = "%-4s %s @%s" % (self.mode, self.req, self.uname)

            if "range" in self.headers:
                try:
                    rval = self.headers["range"].split("=", 1)[1]
                except:
                    rval = self.headers["range"]

                logmsg += " [\033[36m" + rval + "\033[0m]"

            self.log(logmsg)

        # "embedded" resources
        if self.vpath.startswith(".cpr"):
            if self.vpath.startswith(".cpr/ico/"):
                return self.tx_ico(self.vpath.split("/")[-1], exact=True)

            if self.vpath.startswith(".cpr/ssdp"):
                return self.conn.hsrv.ssdp.reply(self)

            if self.vpath.startswith(".cpr/dd/") and self.args.mpmc:
                if self.args.mpmc == ".":
                    raise Pebkac(404)

                loc = self.args.mpmc.rstrip("/") + self.vpath[self.vpath.rfind("/") :]
                h = {"Location": loc, "Cache-Control": "max-age=39"}
                self.reply(b"", 301, headers=h)
                return True

            path_base = os.path.join(self.E.mod, "web")
            static_path = absreal(os.path.join(path_base, self.vpath[5:]))
            if not static_path.startswith(path_base):
                t = "attempted path traversal [{}] => [{}]"
                self.log(t.format(self.vpath, static_path), 1)
                self.tx_404()
                return False

            return self.tx_file(static_path)

        if "cf_challenge" in self.uparam:
            self.reply(self.j2s("cf").encode("utf-8", "replace"))
            return True

        if not self.can_read and not self.can_write and not self.can_get:
            t = "@{} has no access to [{}]"
            self.log(t.format(self.uname, self.vpath))

            if "on403" in self.vn.flags:
                ret = self.on40x(self.vn.flags["on403"], self.vn, self.rem)
                if ret == "true":
                    return True
                elif ret == "false":
                    return False
                elif ret == "allow":
                    self.log("plugin override; access permitted")
                    self.can_read = self.can_write = self.can_move = True
                    self.can_delete = self.can_get = self.can_upget = True
                    self.can_admin = True
                else:
                    return self.tx_404(True)
            else:
                if self.vpath:
                    return self.tx_404(True)

                self.uparam["h"] = ""

        if "tree" in self.uparam:
            return self.tx_tree()

        if "scan" in self.uparam:
            return self.scanvol()

        if self.args.getmod:
            if "delete" in self.uparam:
                return self.handle_rm([])

            if "move" in self.uparam:
                return self.handle_mv()

        if not self.vpath:
            if "reload" in self.uparam:
                return self.handle_reload()

            if "stack" in self.uparam:
                return self.tx_stack()

            if "ups" in self.uparam:
                return self.tx_ups()

            if "k304" in self.uparam:
                return self.set_k304()

            if "setck" in self.uparam:
                return self.setck()

            if "reset" in self.uparam:
                return self.set_cfg_reset()

            if "hc" in self.uparam:
                return self.tx_svcs()

        if "h" in self.uparam:
            return self.tx_mounts()

        # conditional redirect to single volumes
        if self.vpath == "" and not self.ouparam:
            nread = len(self.rvol)
            nwrite = len(self.wvol)
            if nread + nwrite == 1 or (self.rvol == self.wvol and nread == 1):
                if nread == 1:
                    vpath = self.rvol[0]
                else:
                    vpath = self.wvol[0]

                if self.vpath != vpath:
                    self.redirect(vpath, flavor="redirecting to", use302=True)
                    return True

        return self.tx_browser()

--- a/copyparty/httpcli.py
+++ b/copyparty/httpcli.py
@@ -29,7 +29,14 @@
                 self.reply(b"", 301, headers=h)
                 return True

-            static_path = os.path.join(self.E.mod, "web/", self.vpath[5:])
+            path_base = os.path.join(self.E.mod, "web")
+            static_path = absreal(os.path.join(path_base, self.vpath[5:]))
+            if not static_path.startswith(path_base):
+                t = "attempted path traversal [{}] => [{}]"
+                self.log(t.format(self.vpath, static_path), 1)
+                self.tx_404()
+                return False
+
             return self.tx_file(static_path)

         if "cf_challenge" in self.uparam:


Caddy v2.4.6 was discovered to contain an open redirection vulnerability which allows attackers to redirect users to phishing websites via crafted URLs.

import (
	"bytes"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)

func SanitizedPathJoin(root, reqPath string) string {
	reqPath, _ = url.PathUnescape(reqPath)
	if root == "" {
		root = "."
	}

	path := filepath.Join(root, filepath.Clean("/"+reqPath))

	// filepath.Join also cleans the path, and cleaning strips
	// the trailing slash, so we need to re-add it afterwards.
	// if the length is 1, then it's a path to the root,
	// and that should return ".", so we don't append the separator.
	if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
		path += separator
	}

	return path
}

func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, root, urlPath string, repl *caddy.Replacer) browseTemplateContext {
	filesToHide := fsrv.transformHidePaths(repl)

	var dirCount, fileCount int
	fileInfos := []fileInfo{}

	for _, f := range files {
		name := f.Name()

		if fileHidden(name, filesToHide) {
			continue
		}

		isDir := f.IsDir() || isSymlinkTargetDir(f, root, urlPath)

		u := url.URL{Path: url.PathEscape(name)}

		// add the slash after the escape of path to avoid escaping the slash as well
		if isDir {
			u.Path += "/"
			dirCount++
		} else {
			fileCount++
		}

		size := f.Size()
		fileIsSymlink := isSymlink(f)
		if fileIsSymlink {
			path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, f.Name()))
			fileInfo, err := os.Stat(path)
			if err == nil {
				size = fileInfo.Size()
			}
			// An error most likely means the symlink target doesn't exist,
			// which isn't entirely unusual and shouldn't fail the listing.
			// In this case, just use the size of the symlink itself, which
			// was already set above.
		}

		fileInfos = append(fileInfos, fileInfo{
			IsDir:     isDir,
			IsSymlink: fileIsSymlink,
			Name:      name,
			Size:      size,
			URL:       u.String(),
			ModTime:   f.ModTime().UTC(),
			Mode:      f.Mode(),
		})
	}
	name, _ := url.PathUnescape(urlPath)
	return browseTemplateContext{
		Name:     path.Base(name),
		Path:     urlPath,
		CanGoUp:  canGoUp,
		Items:    fileInfos,
		NumDirs:  dirCount,
		NumFiles: fileCount,
	}
}

import (
	"bytes"
	"encoding/json"
	"io"
	"net"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)

func (fsrv *FileServer) directoryListing(files []os.FileInfo, canGoUp bool, root, urlPath string, repl *caddy.Replacer) browseTemplateContext {
	filesToHide := fsrv.transformHidePaths(repl)

	var dirCount, fileCount int
	fileInfos := []fileInfo{}

	for _, f := range files {
		name := f.Name()

		if fileHidden(name, filesToHide) {
			continue
		}

		isDir := f.IsDir() || isSymlinkTargetDir(f, root, urlPath)

		// add the slash after the escape of path to avoid escaping the slash as well
		if isDir {
			name += "/"
			dirCount++
		} else {
			fileCount++
		}

		size := f.Size()
		fileIsSymlink := isSymlink(f)
		if fileIsSymlink {
			path := caddyhttp.SanitizedPathJoin(root, path.Join(urlPath, f.Name()))
			fileInfo, err := os.Stat(path)
			if err == nil {
				size = fileInfo.Size()
			}
			// An error most likely means the symlink target doesn't exist,
			// which isn't entirely unusual and shouldn't fail the listing.
			// In this case, just use the size of the symlink itself, which
			// was already set above.
		}

		u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name

		fileInfos = append(fileInfos, fileInfo{
			IsDir:     isDir,
			IsSymlink: fileIsSymlink,
			Name:      name,
			Size:      size,
			URL:       u.String(),
			ModTime:   f.ModTime().UTC(),
			Mode:      f.Mode(),
		})
	}
	name, _ := url.PathUnescape(urlPath)
	return browseTemplateContext{
		Name:     path.Base(name),
		Path:     urlPath,
		CanGoUp:  canGoUp,
		Items:    fileInfos,
		NumDirs:  dirCount,
		NumFiles: fileCount,
	}
}

--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -4,7 +4,6 @@
 	"io"
 	"net"
 	"net/http"
-	"net/url"
 	"path/filepath"
 	"strconv"
 	"strings"

--- a/modules/caddyhttp/caddyhttp.go
+++ b/modules/caddyhttp/caddyhttp.go
@@ -1,18 +1 @@
-func SanitizedPathJoin(root, reqPath string) string {
-	reqPath, _ = url.PathUnescape(reqPath)
-	if root == "" {
-		root = "."
-	}
-
-	path := filepath.Join(root, filepath.Clean("/"+reqPath))
-
-	// filepath.Join also cleans the path, and cleaning strips
-	// the trailing slash, so we need to re-add it afterwards.
-	// if the length is 1, then it's a path to the root,
-	// and that should return ".", so we don't append the separator.
-	if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
-		path += separator
-	}
-
-	return path
-}
+// It should only be used if another listener wrapper must be placed

--- a/modules/caddyhttp/fileserver/browsetplcontext.go
+++ b/modules/caddyhttp/fileserver/browsetplcontext.go
@@ -13,11 +13,9 @@

 		isDir := f.IsDir() || isSymlinkTargetDir(f, root, urlPath)

-		u := url.URL{Path: url.PathEscape(name)}
-
 		// add the slash after the escape of path to avoid escaping the slash as well
 		if isDir {
-			u.Path += "/"
+			name += "/"
 			dirCount++
 		} else {
 			fileCount++
@@ -36,6 +34,8 @@
 			// In this case, just use the size of the symlink itself, which
 			// was already set above.
 		}
+
+		u := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name

 		fileInfos = append(fileInfos, fileInfo{
 			IsDir:     isDir,


An issue was discovered in free5GC version 3.3.0, allows remote attackers to execute arbitrary code and cause a denial of service (DoS) on AMF component via crafted NGAP message.

func (context *AMFContext) NewAmfRan(conn net.Conn) *AmfRan {
	ran := AmfRan{}
	ran.SupportedTAList = make([]SupportedTAI, 0, MaxNumOfTAI*MaxNumOfBroadcastPLMNs)
	ran.Conn = conn
	ran.Log = logger.NgapLog.WithField(logger.FieldRanAddr, conn.RemoteAddr().String())
	context.AmfRanPool.Store(conn, &ran)
	return &ran
}

func (ranUe *RanUe) UpdateLogFields() {
	if ranUe.Ran != nil && ranUe.Ran.Conn != nil {
		ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, ranUe.Ran.Conn.RemoteAddr().String())

		anTypeStr := ""
		if ranUe.Ran.AnType == models.AccessType__3_GPP_ACCESS {
			anTypeStr = "3GPP"
		} else if ranUe.Ran.AnType == models.AccessType_NON_3_GPP_ACCESS {
			anTypeStr = "Non3GPP"
		}
		ranUe.Log = ranUe.Log.WithField(logger.FieldAmfUeNgapID,
			fmt.Sprintf("RU:%d,AU:%d(%s)", ranUe.RanUeNgapId, ranUe.AmfUeNgapId, anTypeStr))
	} else {
		ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, "no ran conn")
		ranUe.Log = ranUe.Log.WithField(logger.FieldAmfUeNgapID, "RU:,AU:")
	}
}

func Dispatch(conn net.Conn, msg []byte) {
	var ran *context.AmfRan
	amfSelf := context.GetSelf()

	ran, ok := amfSelf.AmfRanFindByConn(conn)
	if !ok {
		logger.NgapLog.Infof("Create a new NG connection for: %s", conn.RemoteAddr().String())
		ran = amfSelf.NewAmfRan(conn)
	}

	if len(msg) == 0 {
		ran.Log.Infof("RAN close the connection.")
		ran.Remove()
		return
	}

	pdu, err := ngap.Decoder(msg)
	if err != nil {
		ran.Log.Errorf("NGAP decode error : %+v", err)
		return
	}

	if ran == nil {
		logger.NgapLog.Error("ran is nil")
		return
	}

	if pdu == nil {
		ran.Log.Error("NGAP Message is nil")
		return
	}

	dispatchMain(ran, pdu)
}

func (context *AMFContext) NewAmfRan(conn net.Conn) *AmfRan {
	ran := AmfRan{}
	ran.SupportedTAList = make([]SupportedTAI, 0, MaxNumOfTAI*MaxNumOfBroadcastPLMNs)
	ran.Conn = conn
	addr := conn.RemoteAddr()
	if addr != nil {
		ran.Log = logger.NgapLog.WithField(logger.FieldRanAddr, addr.String())
	} else {
		ran.Log = logger.NgapLog.WithField(logger.FieldRanAddr, "(nil)")
	}

	context.AmfRanPool.Store(conn, &ran)
	return &ran
}

func (ranUe *RanUe) UpdateLogFields() {
	if ranUe.Ran != nil && ranUe.Ran.Conn != nil {
		addr := ranUe.Ran.Conn.RemoteAddr()
		if addr != nil {
			ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, addr.String())
		} else {
			ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, "(nil)")
		}

		anTypeStr := ""
		if ranUe.Ran.AnType == models.AccessType__3_GPP_ACCESS {
			anTypeStr = "3GPP"
		} else if ranUe.Ran.AnType == models.AccessType_NON_3_GPP_ACCESS {
			anTypeStr = "Non3GPP"
		}
		ranUe.Log = ranUe.Log.WithField(logger.FieldAmfUeNgapID,
			fmt.Sprintf("RU:%d,AU:%d(%s)", ranUe.RanUeNgapId, ranUe.AmfUeNgapId, anTypeStr))
	} else {
		ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, "no ran conn")
		ranUe.Log = ranUe.Log.WithField(logger.FieldAmfUeNgapID, "RU:,AU:")
	}
}

func Dispatch(conn net.Conn, msg []byte) {
	var ran *context.AmfRan
	amfSelf := context.GetSelf()

	ran, ok := amfSelf.AmfRanFindByConn(conn)
	if !ok {
		addr := conn.RemoteAddr()
		if addr == nil {
			logger.NgapLog.Warn("Addr of new NG connection is nii")
			return
		}
		logger.NgapLog.Infof("Create a new NG connection for: %s", addr.String())
		ran = amfSelf.NewAmfRan(conn)
	}

	if len(msg) == 0 {
		ran.Log.Infof("RAN close the connection.")
		ran.Remove()
		return
	}

	pdu, err := ngap.Decoder(msg)
	if err != nil {
		ran.Log.Errorf("NGAP decode error : %+v", err)
		return
	}

	if ran == nil {
		logger.NgapLog.Error("ran is nil")
		return
	}

	if pdu == nil {
		ran.Log.Error("NGAP Message is nil")
		return
	}

	dispatchMain(ran, pdu)
}

--- a/internal/context/context.go
+++ b/internal/context/context.go
@@ -2,7 +2,13 @@
 	ran := AmfRan{}
 	ran.SupportedTAList = make([]SupportedTAI, 0, MaxNumOfTAI*MaxNumOfBroadcastPLMNs)
 	ran.Conn = conn
-	ran.Log = logger.NgapLog.WithField(logger.FieldRanAddr, conn.RemoteAddr().String())
+	addr := conn.RemoteAddr()
+	if addr != nil {
+		ran.Log = logger.NgapLog.WithField(logger.FieldRanAddr, addr.String())
+	} else {
+		ran.Log = logger.NgapLog.WithField(logger.FieldRanAddr, "(nil)")
+	}
+
 	context.AmfRanPool.Store(conn, &ran)
 	return &ran
 }

--- a/internal/context/ran_ue.go
+++ b/internal/context/ran_ue.go
@@ -1,6 +1,11 @@
 func (ranUe *RanUe) UpdateLogFields() {
 	if ranUe.Ran != nil && ranUe.Ran.Conn != nil {
-		ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, ranUe.Ran.Conn.RemoteAddr().String())
+		addr := ranUe.Ran.Conn.RemoteAddr()
+		if addr != nil {
+			ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, addr.String())
+		} else {
+			ranUe.Log = ranUe.Log.WithField(logger.FieldRanAddr, "(nil)")
+		}

 		anTypeStr := ""
 		if ranUe.Ran.AnType == models.AccessType__3_GPP_ACCESS {

--- a/internal/ngap/dispatcher.go
+++ b/internal/ngap/dispatcher.go
@@ -4,7 +4,12 @@

 	ran, ok := amfSelf.AmfRanFindByConn(conn)
 	if !ok {
-		logger.NgapLog.Infof("Create a new NG connection for: %s", conn.RemoteAddr().String())
+		addr := conn.RemoteAddr()
+		if addr == nil {
+			logger.NgapLog.Warn("Addr of new NG connection is nii")
+			return
+		}
+		logger.NgapLog.Infof("Create a new NG connection for: %s", addr.String())
 		ran = amfSelf.NewAmfRan(conn)
 	}


We failed to apply CVE-2023-40611 in 2.7.1 and this vulnerability was marked as fixed then.

Apache Airflow, versions before 2.7.3, is affected by a vulnerability that allows authenticated and DAG-view authorized Users to modify some DAG run detail values when submitting notes. This could have them alter details such as configuration parameters, start date, etc.

Users should upgrade to version 2.7.3 or later which has removed the vulnerability.

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.

class DagRunEditForm(DynamicForm):
    """Form for editing DAG Run.

    We don't actually want to allow editing, so everything is read-only here.
    """

    dag_id = StringField(lazy_gettext("Dag Id"), widget=BS3TextFieldROWidget())
    start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
    end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
    run_id = StringField(lazy_gettext("Run Id"), widget=BS3TextFieldROWidget())
    state = StringField(lazy_gettext("State"), widget=BS3TextFieldROWidget())
    execution_date = DateTimeWithTimezoneField(
        lazy_gettext("Logical Date"),
        widget=AirflowDateTimePickerROWidget(),
    )
    conf = TextAreaField(lazy_gettext("Conf"), widget=BS3TextAreaROWidget())
    note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())

    def populate_obj(self, item):
        """Populates the attributes of the passed obj with data from the form's fields."""
        super().populate_obj(item)
        item.run_type = DagRunType.from_run_id(item.run_id)
        if item.conf:
            item.conf = json.loads(item.conf)

class TaskInstanceEditForm(DynamicForm):
    """Form for editing TaskInstance."""

    dag_id = StringField(lazy_gettext("Dag Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget())
    task_id = StringField(
        lazy_gettext("Task Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget()
    )
    start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
    end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
    state = SelectField(
        lazy_gettext("State"),
        choices=(
            ("success", "success"),
            ("running", "running"),
            ("failed", "failed"),
            ("up_for_retry", "up_for_retry"),
        ),
        widget=Select2Widget(),
        validators=[InputRequired()],
    )
    execution_date = DateTimeWithTimezoneField(
        lazy_gettext("Logical Date"),
        widget=AirflowDateTimePickerROWidget(),
        validators=[InputRequired()],
    )
    note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())

class DagRunEditForm(DynamicForm):
    """Form for editing DAG Run.

    Only note field is editable, so everything else is read-only here.
    """

    dag_id = StringField(lazy_gettext("Dag Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
    start_date = DateTimeWithTimezoneField(
        lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
    )
    end_date = DateTimeWithTimezoneField(
        lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
    )
    run_id = StringField(lazy_gettext("Run Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
    state = StringField(lazy_gettext("State"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
    execution_date = DateTimeWithTimezoneField(
        lazy_gettext("Logical Date"),
        validators=[ReadOnly()],
        widget=AirflowDateTimePickerROWidget(),
    )
    conf = TextAreaField(lazy_gettext("Conf"), validators=[ReadOnly()], widget=BS3TextAreaROWidget())
    note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())

    def populate_obj(self, item):
        """Populates the attributes of the passed obj with data from the form's not-read-only fields."""
        for name, field in self._fields.items():
            if not field.flags.readonly:
                field.populate_obj(item, name)
        item.run_type = DagRunType.from_run_id(item.run_id)
        if item.conf:
            item.conf = json.loads(item.conf)

class TaskInstanceEditForm(DynamicForm):
    """Form for editing TaskInstance.

    Only note and state fields are editable, so everything else is read-only here.
    """

    dag_id = StringField(
        lazy_gettext("Dag Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
    )
    task_id = StringField(
        lazy_gettext("Task Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
    )
    start_date = DateTimeWithTimezoneField(
        lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
    )
    end_date = DateTimeWithTimezoneField(
        lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
    )
    state = SelectField(
        lazy_gettext("State"),
        choices=(
            ("success", "success"),
            ("running", "running"),
            ("failed", "failed"),
            ("up_for_retry", "up_for_retry"),
        ),
        widget=Select2Widget(),
        validators=[InputRequired()],
    )
    execution_date = DateTimeWithTimezoneField(
        lazy_gettext("Logical Date"),
        widget=AirflowDateTimePickerROWidget(),
        validators=[InputRequired(), ReadOnly()],
    )
    note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())

    def populate_obj(self, item):
        """Populates the attributes of the passed obj with data from the form's not-read-only fields."""
        for name, field in self._fields.items():
            if not field.flags.readonly:
                field.populate_obj(item, name)

class ReadOnly:
    """Adds readonly flag to a field.

    When using this you normally will need to override the form's populate_obj method,
    so field.populate_obj is not called for read-only fields.
    """

    def __call__(self, form, field):
        field.flags.readonly = True

--- a/airflow/www/forms.py
+++ b/airflow/www/forms.py
@@ -1,24 +1,31 @@
 class DagRunEditForm(DynamicForm):
     """Form for editing DAG Run.

-    We don't actually want to allow editing, so everything is read-only here.
+    Only note field is editable, so everything else is read-only here.
     """

-    dag_id = StringField(lazy_gettext("Dag Id"), widget=BS3TextFieldROWidget())
-    start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
-    end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
-    run_id = StringField(lazy_gettext("Run Id"), widget=BS3TextFieldROWidget())
-    state = StringField(lazy_gettext("State"), widget=BS3TextFieldROWidget())
+    dag_id = StringField(lazy_gettext("Dag Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
+    start_date = DateTimeWithTimezoneField(
+        lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
+    )
+    end_date = DateTimeWithTimezoneField(
+        lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
+    )
+    run_id = StringField(lazy_gettext("Run Id"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
+    state = StringField(lazy_gettext("State"), validators=[ReadOnly()], widget=BS3TextFieldROWidget())
     execution_date = DateTimeWithTimezoneField(
         lazy_gettext("Logical Date"),
+        validators=[ReadOnly()],
         widget=AirflowDateTimePickerROWidget(),
     )
-    conf = TextAreaField(lazy_gettext("Conf"), widget=BS3TextAreaROWidget())
+    conf = TextAreaField(lazy_gettext("Conf"), validators=[ReadOnly()], widget=BS3TextAreaROWidget())
     note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())

     def populate_obj(self, item):
-        """Populates the attributes of the passed obj with data from the form's fields."""
-        super().populate_obj(item)
+        """Populates the attributes of the passed obj with data from the form's not-read-only fields."""
+        for name, field in self._fields.items():
+            if not field.flags.readonly:
+                field.populate_obj(item, name)
         item.run_type = DagRunType.from_run_id(item.run_id)
         if item.conf:
             item.conf = json.loads(item.conf)

--- a/airflow/www/forms.py
+++ b/airflow/www/forms.py
@@ -1,12 +1,21 @@
 class TaskInstanceEditForm(DynamicForm):
-    """Form for editing TaskInstance."""
+    """Form for editing TaskInstance.

-    dag_id = StringField(lazy_gettext("Dag Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget())
+    Only note and state fields are editable, so everything else is read-only here.
+    """
+
+    dag_id = StringField(
+        lazy_gettext("Dag Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
+    )
     task_id = StringField(
-        lazy_gettext("Task Id"), validators=[InputRequired()], widget=BS3TextFieldROWidget()
+        lazy_gettext("Task Id"), validators=[InputRequired(), ReadOnly()], widget=BS3TextFieldROWidget()
     )
-    start_date = DateTimeWithTimezoneField(lazy_gettext("Start Date"), widget=AirflowDateTimePickerROWidget())
-    end_date = DateTimeWithTimezoneField(lazy_gettext("End Date"), widget=AirflowDateTimePickerROWidget())
+    start_date = DateTimeWithTimezoneField(
+        lazy_gettext("Start Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
+    )
+    end_date = DateTimeWithTimezoneField(
+        lazy_gettext("End Date"), validators=[ReadOnly()], widget=AirflowDateTimePickerROWidget()
+    )
     state = SelectField(
         lazy_gettext("State"),
         choices=(
@@ -21,6 +30,12 @@
     execution_date = DateTimeWithTimezoneField(
         lazy_gettext("Logical Date"),
         widget=AirflowDateTimePickerROWidget(),
-        validators=[InputRequired()],
+        validators=[InputRequired(), ReadOnly()],
     )
     note = TextAreaField(lazy_gettext("User Note"), widget=BS3TextAreaFieldWidget())
+
+    def populate_obj(self, item):
+        """Populates the attributes of the passed obj with data from the form's not-read-only fields."""
+        for name, field in self._fields.items():
+            if not field.flags.readonly:
+                field.populate_obj(item, name)

--- /dev/null
+++ b/airflow/www/forms.py
@@ -0,0 +1,9 @@
+class ReadOnly:
+    """Adds readonly flag to a field.
+
+    When using this you normally will need to override the form's populate_obj method,
+    so field.populate_obj is not called for read-only fields.
+    """
+
+    def __call__(self, form, field):
+        field.flags.readonly = True