An Insecure Direct Object Reference (IDOR) vulnerability exists in the lunary-ai/lunary repository, version 0.3.0, within the project update endpoint. The vulnerability allows authenticated users to modify the name of any project within the system without proper authorization checks, by directly referencing the project's ID in the PATCH request to the '/v1/projects/:projectId' endpoint. This issue arises because the endpoint does not verify if the provided project ID belongs to the currently authenticated user, enabling unauthorized modifications across different organizational projects.

Authorization Bypass Through User-Controlled Key

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

projects.patch("/:projectId", async (ctx: Context) => {
  const { projectId } = ctx.params
  const bodySchema = z.object({
    name: z.string(),
  })
  const { name } = bodySchema.parse(ctx.request.body)

  await sql`
      update project
      set
        name = ${name}
      where
        id = ${projectId}
    `
  ctx.status = 200
})

export async function verifyProjectAccess(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
}

projects.delete("/:projectId", async (ctx: Context) => {
  const { projectId } = ctx.params
  const { orgId, userId } = ctx.state
  console.log(ctx.state)

  const hasProjectAccess = await verifyProjectAccess(projectId, userId)
  const [user] = await sql`select * from account where id = ${userId}`

  if (!hasProjectAccess) {
    ctx.throw(401, "Not allowed")
  }

  if (user.role !== "admin") {
    ctx.throw(403, "You must be an admin to delete a project")
  }

  const [{ count }] =
    await sql`select count(*)::int from  project where org_id = ${orgId}`

  if (count > 1) {
    await sql`delete from project where id = ${projectId}`
    ctx.status = 200
    ctx.body = {}
    return
  } else {
    ctx.status = 422

    ctx.body = {
      error: "Deletion Failed",
      message: "An organization must have at least one project.",
    }
    return
  }
})

projects.patch("/:projectId", async (ctx: Context) => {
  const { projectId } = ctx.params
  const { userId } = ctx.params

  const hasProjectAccess = await checkProjectAccess(projectId, userId)
  if (!hasProjectAccess) {
    ctx.throw(401, "Unauthorized")
  }

  const bodySchema = z.object({
    name: z.string(),
  })
  const { name } = bodySchema.parse(ctx.request.body)

  await sql`
      update project
      set
        name = ${name}
      where
        id = ${projectId}
    `
  ctx.status = 200
})

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
}

projects.delete("/:projectId", async (ctx: Context) => {
  const { projectId } = ctx.params
  const { orgId, userId } = ctx.state

  const hasProjectAccess = await checkProjectAccess(projectId, userId)
  const [user] = await sql`select * from account where id = ${userId}`

  if (!hasProjectAccess) {
    ctx.throw(401, "Not allowed")
  }

  if (user.role !== "admin") {
    ctx.throw(403, "You must be an admin to delete a project")
  }

  const [{ count }] =
    await sql`select count(*)::int from  project where org_id = ${orgId}`

  if (count > 1) {
    await sql`delete from project where id = ${projectId}`
    ctx.status = 200
    ctx.body = {}
    return
  } else {
    ctx.status = 422

    ctx.body = {
      error: "Deletion Failed",
      message: "An organization must have at least one project.",
    }
    return
  }
})

--- a/packages/backend/src/api/v1/projects/index.ts
+++ b/packages/backend/src/api/v1/projects/index.ts
@@ -1,5 +1,12 @@
 projects.patch("/:projectId", async (ctx: Context) => {
   const { projectId } = ctx.params
+  const { userId } = ctx.params
+
+  const hasProjectAccess = await checkProjectAccess(projectId, userId)
+  if (!hasProjectAccess) {
+    ctx.throw(401, "Unauthorized")
+  }
+
   const bodySchema = z.object({
     name: z.string(),
   })

--- a/packages/backend/src/utils/authorization.ts
+++ b/packages/backend/src/utils/authorization.ts
@@ -1,4 +1,4 @@
-export async function verifyProjectAccess(projectId: string, userId: string) {
+export async function checkProjectAccess(projectId: string, userId: string) {
   const [{ exists: hasAccess }] = await sql`
       select exists (
         select 1

--- a/packages/backend/src/api/v1/projects/index.ts
+++ b/packages/backend/src/api/v1/projects/index.ts
@@ -1,9 +1,8 @@
 projects.delete("/:projectId", async (ctx: Context) => {
   const { projectId } = ctx.params
   const { orgId, userId } = ctx.state
-  console.log(ctx.state)

-  const hasProjectAccess = await verifyProjectAccess(projectId, userId)
+  const hasProjectAccess = await checkProjectAccess(projectId, userId)
   const [user] = await sql`select * from account where id = ${userId}`

   if (!hasProjectAccess) {


Gogs is an open source self-hosted Git service. A malicious user is able to commit and edit a crafted symlink file to a repository to gain SSH access to the server. The vulnerability is fixed in 0.13.1.

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.

func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
	c.PageIs("Edit")
	c.RequireHighlightJS()
	c.RequireSimpleMDE()
	c.Data["IsNewFile"] = isNewFile

	oldBranchName := c.Repo.BranchName
	branchName := oldBranchName
	oldTreePath := c.Repo.TreePath
	lastCommit := f.LastCommit
	f.LastCommit = c.Repo.Commit.ID.String()

	if f.IsNewBrnach() {
		branchName = f.NewBranchName
	}

	f.TreePath = pathutil.Clean(f.TreePath)
	treeNames, treePaths := getParentTreeFields(f.TreePath)

	c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath)
	c.Data["TreePath"] = f.TreePath
	c.Data["TreeNames"] = treeNames
	c.Data["TreePaths"] = treePaths
	c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName
	c.Data["FileContent"] = f.Content
	c.Data["commit_summary"] = f.CommitSummary
	c.Data["commit_message"] = f.CommitMessage
	c.Data["commit_choice"] = f.CommitChoice
	c.Data["new_branch_name"] = branchName
	c.Data["last_commit"] = f.LastCommit
	c.Data["MarkdownFileExts"] = strings.Join(conf.Markdown.FileExtensions, ",")
	c.Data["LineWrapExtensions"] = strings.Join(conf.Repository.Editor.LineWrapExtensions, ",")
	c.Data["PreviewableFileModes"] = strings.Join(conf.Repository.Editor.PreviewableFileModes, ",")

	if c.HasError() {
		c.Success(tmplEditorEdit)
		return
	}

	if f.TreePath == "" {
		c.FormErr("TreePath")
		c.RenderWithErr(c.Tr("repo.editor.filename_cannot_be_empty"), tmplEditorEdit, &f)
		return
	}

	if oldBranchName != branchName {
		if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
			c.FormErr("NewBranchName")
			c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), tmplEditorEdit, &f)
			return
		}
	}

	var newTreePath string
	for index, part := range treeNames {
		newTreePath = path.Join(newTreePath, part)
		entry, err := c.Repo.Commit.TreeEntry(newTreePath)
		if err != nil {
			if gitutil.IsErrRevisionNotExist(err) {
				// Means there is no item with that name, so we're good
				break
			}

			c.Error(err, "get tree entry")
			return
		}
		if index != len(treeNames)-1 {
			if !entry.IsTree() {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), tmplEditorEdit, &f)
				return
			}
		} else {
			if entry.IsSymlink() {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), tmplEditorEdit, &f)
				return
			} else if entry.IsTree() {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), tmplEditorEdit, &f)
				return
			}
		}
	}

	if !isNewFile {
		_, err := c.Repo.Commit.TreeEntry(oldTreePath)
		if err != nil {
			if gitutil.IsErrRevisionNotExist(err) {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), tmplEditorEdit, &f)
			} else {
				c.Error(err, "get tree entry")
			}
			return
		}
		if lastCommit != c.Repo.CommitID {
			files, err := c.Repo.Commit.FilesChangedAfter(lastCommit)
			if err != nil {
				c.Error(err, "get changed files")
				return
			}

			for _, file := range files {
				if file == f.TreePath {
					c.RenderWithErr(c.Tr("repo.editor.file_changed_while_editing", c.Repo.RepoLink+"/compare/"+lastCommit+"..."+c.Repo.CommitID), tmplEditorEdit, &f)
					return
				}
			}
		}
	}

	if oldTreePath != f.TreePath {
		// We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber.
		entry, err := c.Repo.Commit.TreeEntry(f.TreePath)
		if err != nil {
			if !gitutil.IsErrRevisionNotExist(err) {
				c.Error(err, "get tree entry")
				return
			}
		}
		if entry != nil {
			c.FormErr("TreePath")
			c.RenderWithErr(c.Tr("repo.editor.file_already_exists", f.TreePath), tmplEditorEdit, &f)
			return
		}
	}

	message := strings.TrimSpace(f.CommitSummary)
	if message == "" {
		if isNewFile {
			message = c.Tr("repo.editor.add", f.TreePath)
		} else {
			message = c.Tr("repo.editor.update", f.TreePath)
		}
	}

	f.CommitMessage = strings.TrimSpace(f.CommitMessage)
	if len(f.CommitMessage) > 0 {
		message += "\n\n" + f.CommitMessage
	}

	if err := c.Repo.Repository.UpdateRepoFile(c.User, database.UpdateRepoFileOptions{
		OldBranch:   oldBranchName,
		NewBranch:   branchName,
		OldTreeName: oldTreePath,
		NewTreeName: f.TreePath,
		Message:     message,
		Content:     strings.ReplaceAll(f.Content, "\r", ""),
		IsNewFile:   isNewFile,
	}); err != nil {
		log.Error("Failed to update repo file: %v", err)
		c.FormErr("TreePath")
		c.RenderWithErr(c.Tr("repo.editor.fail_to_update_file", f.TreePath, errors.InternalServerError), tmplEditorEdit, &f)
		return
	}

	if f.IsNewBrnach() && c.Repo.PullRequest.Allowed {
		c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
	} else {
		c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath)
	}
}

func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
	c.PageIs("Edit")
	c.RequireHighlightJS()
	c.RequireSimpleMDE()
	c.Data["IsNewFile"] = isNewFile

	oldBranchName := c.Repo.BranchName
	branchName := oldBranchName
	oldTreePath := c.Repo.TreePath
	lastCommit := f.LastCommit
	f.LastCommit = c.Repo.Commit.ID.String()

	if f.IsNewBrnach() {
		branchName = f.NewBranchName
	}

	f.TreePath = pathutil.Clean(f.TreePath)
	treeNames, treePaths := getParentTreeFields(f.TreePath)

	c.Data["ParentTreePath"] = path.Dir(c.Repo.TreePath)
	c.Data["TreePath"] = f.TreePath
	c.Data["TreeNames"] = treeNames
	c.Data["TreePaths"] = treePaths
	c.Data["BranchLink"] = c.Repo.RepoLink + "/src/" + branchName
	c.Data["FileContent"] = f.Content
	c.Data["commit_summary"] = f.CommitSummary
	c.Data["commit_message"] = f.CommitMessage
	c.Data["commit_choice"] = f.CommitChoice
	c.Data["new_branch_name"] = branchName
	c.Data["last_commit"] = f.LastCommit
	c.Data["MarkdownFileExts"] = strings.Join(conf.Markdown.FileExtensions, ",")
	c.Data["LineWrapExtensions"] = strings.Join(conf.Repository.Editor.LineWrapExtensions, ",")
	c.Data["PreviewableFileModes"] = strings.Join(conf.Repository.Editor.PreviewableFileModes, ",")

	if c.HasError() {
		c.Success(tmplEditorEdit)
		return
	}

	if f.TreePath == "" {
		c.FormErr("TreePath")
		c.RenderWithErr(c.Tr("repo.editor.filename_cannot_be_empty"), tmplEditorEdit, &f)
		return
	}

	if oldBranchName != branchName {
		if _, err := c.Repo.Repository.GetBranch(branchName); err == nil {
			c.FormErr("NewBranchName")
			c.RenderWithErr(c.Tr("repo.editor.branch_already_exists", branchName), tmplEditorEdit, &f)
			return
		}
	}

	var newTreePath string
	for index, part := range treeNames {
		newTreePath = path.Join(newTreePath, part)
		entry, err := c.Repo.Commit.TreeEntry(newTreePath)
		if err != nil {
			if gitutil.IsErrRevisionNotExist(err) {
				// Means there is no item with that name, so we're good
				break
			}

			c.Error(err, "get tree entry")
			return
		}
		if index != len(treeNames)-1 {
			if !entry.IsTree() {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), tmplEditorEdit, &f)
				return
			}
		} else {
			// \ud83d\udea8 SECURITY: Do not allow editing if the target file is a symlink.
			if entry.IsSymlink() {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), tmplEditorEdit, &f)
				return
			} else if entry.IsTree() {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), tmplEditorEdit, &f)
				return
			}
		}
	}

	if !isNewFile {
		entry, err := c.Repo.Commit.TreeEntry(oldTreePath)
		if err != nil {
			if gitutil.IsErrRevisionNotExist(err) {
				c.FormErr("TreePath")
				c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), tmplEditorEdit, &f)
			} else {
				c.Error(err, "get tree entry")
			}
			return
		}

		// \ud83d\udea8 SECURITY: Do not allow editing if the old file is a symlink.
		if entry.IsSymlink() {
			c.FormErr("TreePath")
			c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", oldTreePath), tmplEditorEdit, &f)
			return
		}

		if lastCommit != c.Repo.CommitID {
			files, err := c.Repo.Commit.FilesChangedAfter(lastCommit)
			if err != nil {
				c.Error(err, "get changed files")
				return
			}

			for _, file := range files {
				if file == f.TreePath {
					c.RenderWithErr(c.Tr("repo.editor.file_changed_while_editing", c.Repo.RepoLink+"/compare/"+lastCommit+"..."+c.Repo.CommitID), tmplEditorEdit, &f)
					return
				}
			}
		}
	}

	if oldTreePath != f.TreePath {
		// We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber.
		entry, err := c.Repo.Commit.TreeEntry(f.TreePath)
		if err != nil {
			if !gitutil.IsErrRevisionNotExist(err) {
				c.Error(err, "get tree entry")
				return
			}
		}
		if entry != nil {
			c.FormErr("TreePath")
			c.RenderWithErr(c.Tr("repo.editor.file_already_exists", f.TreePath), tmplEditorEdit, &f)
			return
		}
	}

	message := strings.TrimSpace(f.CommitSummary)
	if message == "" {
		if isNewFile {
			message = c.Tr("repo.editor.add", f.TreePath)
		} else {
			message = c.Tr("repo.editor.update", f.TreePath)
		}
	}

	f.CommitMessage = strings.TrimSpace(f.CommitMessage)
	if len(f.CommitMessage) > 0 {
		message += "\n\n" + f.CommitMessage
	}

	if err := c.Repo.Repository.UpdateRepoFile(c.User, database.UpdateRepoFileOptions{
		OldBranch:   oldBranchName,
		NewBranch:   branchName,
		OldTreeName: oldTreePath,
		NewTreeName: f.TreePath,
		Message:     message,
		Content:     strings.ReplaceAll(f.Content, "\r", ""),
		IsNewFile:   isNewFile,
	}); err != nil {
		log.Error("Failed to update repo file: %v", err)
		c.FormErr("TreePath")
		c.RenderWithErr(c.Tr("repo.editor.fail_to_update_file", f.TreePath, errors.InternalServerError), tmplEditorEdit, &f)
		return
	}

	if f.IsNewBrnach() && c.Repo.PullRequest.Allowed {
		c.Redirect(c.Repo.PullRequestURL(oldBranchName, f.NewBranchName))
	} else {
		c.Redirect(c.Repo.RepoLink + "/src/" + branchName + "/" + f.TreePath)
	}
}

--- a/internal/route/repo/editor.go
+++ b/internal/route/repo/editor.go
@@ -71,6 +71,7 @@
 				return
 			}
 		} else {
+			// \ud83d\udea8 SECURITY: Do not allow editing if the target file is a symlink.
 			if entry.IsSymlink() {
 				c.FormErr("TreePath")
 				c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), tmplEditorEdit, &f)
@@ -84,7 +85,7 @@
 	}

 	if !isNewFile {
-		_, err := c.Repo.Commit.TreeEntry(oldTreePath)
+		entry, err := c.Repo.Commit.TreeEntry(oldTreePath)
 		if err != nil {
 			if gitutil.IsErrRevisionNotExist(err) {
 				c.FormErr("TreePath")
@@ -94,6 +95,14 @@
 			}
 			return
 		}
+
+		// \ud83d\udea8 SECURITY: Do not allow editing if the old file is a symlink.
+		if entry.IsSymlink() {
+			c.FormErr("TreePath")
+			c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", oldTreePath), tmplEditorEdit, &f)
+			return
+		}
+
 		if lastCommit != c.Repo.CommitID {
 			files, err := c.Repo.Commit.FilesChangedAfter(lastCommit)
 			if err != nil {


The package p4 before 0.0.7 are vulnerable to Command Injection via the run() function due to improper input sanitization

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.

function runCommand(command, args, done) {
    if(typeof args === "function") {
        done = args;
        args = "";
    }

    exec("p4 " + command + " " + (args || ""), function(err, stdOut, stdErr) {
        if(err) return done(err);
        if(stdErr) return done(new Error(stdErr));

        done(null, stdOut);
    });
}

function runCommand(command, args, done) {
    if(typeof args === "function") {
        done = args;
        args = "";
    }

    if(!Array.isArray(args)) {
        args = [args];
    }
    args.unshift(command);

    var child = spawn("p4", args);
    var stdOutBuf = "";
    var stdErrBuf = "";

    child.stdout.on("data", (data) => stdOutBuf += data);
    child.stderr.on("data", (data) => stdErrBuf += data)
    child.on("exit", (code) => {
        if (code !== 0) {
            return done(new Error(`p4 subcommand exited with return code ${}`));
        }

        if (stdErrBuf.length > 0) {
            return done(new Error(stdErrBuf));
        }

        done(null, stdOutBuf);
    });
}

--- a/p4.js
+++ b/p4.js
@@ -4,10 +4,26 @@
         args = "";
     }

-    exec("p4 " + command + " " + (args || ""), function(err, stdOut, stdErr) {
-        if(err) return done(err);
-        if(stdErr) return done(new Error(stdErr));
+    if(!Array.isArray(args)) {
+        args = [args];
+    }
+    args.unshift(command);

-        done(null, stdOut);
+    var child = spawn("p4", args);
+    var stdOutBuf = "";
+    var stdErrBuf = "";
+
+    child.stdout.on("data", (data) => stdOutBuf += data);
+    child.stderr.on("data", (data) => stdErrBuf += data)
+    child.on("exit", (code) => {
+        if (code !== 0) {
+            return done(new Error(`p4 subcommand exited with return code ${}`));
+        }
+
+        if (stdErrBuf.length > 0) {
+            return done(new Error(stdErrBuf));
+        }
+
+        done(null, stdOutBuf);
     });
 }


A file overwrite vulnerability exists in gaizhenbiao/chuanhuchatgpt versions <= 20240410. This vulnerability allows an attacker to gain unauthorized access to overwrite critical configuration files within the system. Exploiting this vulnerability can lead to unauthorized changes in system behavior or security settings. Additionally, tampering with these configuration files can result in a denial of service (DoS) condition, disrupting normal system operation.

Externally Controlled Reference to a Resource in Another Sphere

The product uses an externally controlled name or reference that resolves to a resource that is outside of the intended control sphere.

def update_chuanhu():
    from .repo import background_update

    print("[Updater] Trying to update...")
    update_status = background_update()
    if update_status == "success":
        logging.info("Successfully updated, restart needed")
        status = 'success'
        return gr.Markdown(value=i18n("\u66f4\u65b0\u6210\u529f\uff0c\u8bf7\u91cd\u542f\u672c\u7a0b\u5e8f") + status)
    else:
        status = 'failure'
        return gr.Markdown(
            value=i18n(
                "\u66f4\u65b0\u5931\u8d25\uff0c\u8bf7\u5c1d\u8bd5[\u624b\u52a8\u66f4\u65b0](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/\u4f7f\u7528\u6559\u7a0b#\u624b\u52a8\u66f4\u65b0)"
            )
            + status
        )

def update_chuanhu(username):
    if username not in admin_list:
        return gr.Markdown(value=i18n("no_permission_to_update_description"))
    from .repo import background_update

    print("[Updater] Trying to update...")
    update_status = background_update()
    if update_status == "success":
        logging.info("Successfully updated, restart needed")
        status = 'success'
        return gr.Markdown(value=i18n("\u66f4\u65b0\u6210\u529f\uff0c\u8bf7\u91cd\u542f\u672c\u7a0b\u5e8f") + status)
    else:
        status = 'failure'
        return gr.Markdown(
            value=i18n(
                "\u66f4\u65b0\u5931\u8d25\uff0c\u8bf7\u5c1d\u8bd5[\u624b\u52a8\u66f4\u65b0](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/\u4f7f\u7528\u6559\u7a0b#\u624b\u52a8\u66f4\u65b0)"
            )
            + status
        )

--- a/modules/utils.py
+++ b/ChuanhuChatbot.py
@@ -1,17 +1 @@
-def update_chuanhu():
-    from .repo import background_update
-
-    print("[Updater] Trying to update...")
-    update_status = background_update()
-    if update_status == "success":
-        logging.info("Successfully updated, restart needed")
-        status = 'success'
-        return gr.Markdown(value=i18n("\u66f4\u65b0\u6210\u529f\uff0c\u8bf7\u91cd\u542f\u672c\u7a0b\u5e8f") + status)
-    else:
-        status = 'failure'
-        return gr.Markdown(
-            value=i18n(
-                "\u66f4\u65b0\u5931\u8d25\uff0c\u8bf7\u5c1d\u8bd5[\u624b\u52a8\u66f4\u65b0](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/\u4f7f\u7528\u6559\u7a0b#\u624b\u52a8\u66f4\u65b0)"
-            )
-            + status
-        )
+        [user_name],

--- /dev/null
+++ b/ChuanhuChatbot.py
@@ -0,0 +1,19 @@
+def update_chuanhu(username):
+    if username not in admin_list:
+        return gr.Markdown(value=i18n("no_permission_to_update_description"))
+    from .repo import background_update
+
+    print("[Updater] Trying to update...")
+    update_status = background_update()
+    if update_status == "success":
+        logging.info("Successfully updated, restart needed")
+        status = 'success'
+        return gr.Markdown(value=i18n("\u66f4\u65b0\u6210\u529f\uff0c\u8bf7\u91cd\u542f\u672c\u7a0b\u5e8f") + status)
+    else:
+        status = 'failure'
+        return gr.Markdown(
+            value=i18n(
+                "\u66f4\u65b0\u5931\u8d25\uff0c\u8bf7\u5c1d\u8bd5[\u624b\u52a8\u66f4\u65b0](https://github.com/GaiZhenbiao/ChuanhuChatGPT/wiki/\u4f7f\u7528\u6559\u7a0b#\u624b\u52a8\u66f4\u65b0)"
+            )
+            + status
+        )

--- /dev/null
+++ b/ChuanhuChatbot.py
@@ -0,0 +1 @@
+from modules.config import retrieve_proxy, hide_history_when_not_logged_in, admin_list

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

cd /workspace/chuanhuchatgpt
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2024-5823/bin/python  hand_test.py


An issue in the component /admin/backups/work-dir of Sonic v1.0.4 allows attackers to execute a directory traversal.

func (b *backupServiceImpl) BackupWholeSite(ctx context.Context, toBackupItems []string) (*dto.BackupDTO, error) {
	backupFilename := consts.SonicBackupPrefix + time.Now().Format("2006-01-02-15-04-05") + util.GenUUIDWithOutDash() + ".zip"
	backupFilePath := config.BackupDir

	if _, err := os.Stat(backupFilePath); os.IsNotExist(err) {
		err = os.MkdirAll(backupFilePath, os.ModePerm)
		if err != nil {
			return nil, xerr.NoType.Wrap(err).WithMsg("create dir err")
		}
	} else if err != nil {
		return nil, xerr.NoType.Wrap(err).WithMsg("get fileInfo")
	}

	backupFile := filepath.Join(backupFilePath, backupFilename)

	toBackupPaths := []string{}
	for _, toBackupItem := range toBackupItems {
		toBackupPath := filepath.Join(b.Config.Sonic.WorkDir, toBackupItem)
		toBackupPaths = append(toBackupPaths, toBackupPath)
	}

	err := util.ZipFile(backupFile, toBackupPaths...)
	if err != nil {
		return nil, err
	}
	return b.buildBackupDTO(ctx, string(service.WholeSite), backupFile)
}

func (b *backupServiceImpl) BackupWholeSite(ctx context.Context, toBackupItems []string) (*dto.BackupDTO, error) {
	backupFilename := consts.SonicBackupPrefix + time.Now().Format("2006-01-02-15-04-05") + util.GenUUIDWithOutDash() + ".zip"
	backupFilePath := config.BackupDir

	if _, err := os.Stat(backupFilePath); os.IsNotExist(err) {
		err = os.MkdirAll(backupFilePath, os.ModePerm)
		if err != nil {
			return nil, xerr.NoType.Wrap(err).WithMsg("create dir err")
		}
	} else if err != nil {
		return nil, xerr.NoType.Wrap(err).WithMsg("get fileInfo")
	}

	backupFile := filepath.Join(backupFilePath, backupFilename)

	toBackupPaths := []string{}
	for _, toBackupItem := range toBackupItems {
		toBackupPath := filepath.Clean(filepath.Join(b.Config.Sonic.WorkDir, toBackupItem))
		if !strings.HasPrefix(toBackupPath, b.Config.Sonic.WorkDir) {
			continue
		}
		toBackupPaths = append(toBackupPaths, toBackupPath)
	}

	err := util.ZipFile(backupFile, toBackupPaths...)
	if err != nil {
		return nil, err
	}
	return b.buildBackupDTO(ctx, string(service.WholeSite), backupFile)
}

--- a/service/impl/backup.go
+++ b/service/impl/backup.go
@@ -15,7 +15,10 @@

 	toBackupPaths := []string{}
 	for _, toBackupItem := range toBackupItems {
-		toBackupPath := filepath.Join(b.Config.Sonic.WorkDir, toBackupItem)
+		toBackupPath := filepath.Clean(filepath.Join(b.Config.Sonic.WorkDir, toBackupItem))
+		if !strings.HasPrefix(toBackupPath, b.Config.Sonic.WorkDir) {
+			continue
+		}
 		toBackupPaths = append(toBackupPaths, toBackupPath)
 	}


The allow_execmod plugin for setroubleshoot before 3.2.23 allows local users to execute arbitrary commands by triggering an execmod SELinux denial with a crafted binary filename, related to the commands.getstatusoutput function.

    def analyze(self, avc):
        import commands
        if avc.has_any_access_in(['execmod']):
            # MATCH
            if (commands.getstatusoutput("eu-readelf -d %s | fgrep -q TEXTREL" % avc.tpath)[0] == 1):
                return self.report(("unsafe"))

            mcon = selinux.matchpathcon(avc.tpath.strip('"'), S_IFREG)[1]
            if mcon.split(":")[2] == "lib_t":
                return self.report()
        return None

    def analyze(self, avc):
        import subprocess
        if avc.has_any_access_in(['execmod']):
            # MATCH
            # from https://docs.python.org/2.7/library/subprocess.html#replacing-shell-pipeline
            p1 = subprocess.Popen(['eu-readelf', '-d', avc.tpath], stdout=subprocess.PIPE)
            p2 = subprocess.Popen(["fgrep", "-q", "TEXTREL"], stdin=p1.stdout, stdout=subprocess.PIPE)
            p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
            p1.wait()
            p2.wait()
            if p2.returncode == 1:
                return self.report(("unsafe"))

            mcon = selinux.matchpathcon(avc.tpath.strip('"'), S_IFREG)[1]
            if mcon.split(":")[2] == "lib_t":
                return self.report()
        return None

--- a/plugins/src/allow_execmod.py
+++ b/plugins/src/allow_execmod.py
@@ -1,8 +1,14 @@
     def analyze(self, avc):
-        import commands
+        import subprocess
         if avc.has_any_access_in(['execmod']):
             # MATCH
-            if (commands.getstatusoutput("eu-readelf -d %s | fgrep -q TEXTREL" % avc.tpath)[0] == 1):
+            # from https://docs.python.org/2.7/library/subprocess.html#replacing-shell-pipeline
+            p1 = subprocess.Popen(['eu-readelf', '-d', avc.tpath], stdout=subprocess.PIPE)
+            p2 = subprocess.Popen(["fgrep", "-q", "TEXTREL"], stdin=p1.stdout, stdout=subprocess.PIPE)
+            p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
+            p1.wait()
+            p2.wait()
+            if p2.returncode == 1:
                 return self.report(("unsafe"))

             mcon = selinux.matchpathcon(avc.tpath.strip('"'), S_IFREG)[1]


UltiMaker Cura slicer versions 5.7.0-beta.1 through 5.7.2 are vulnerable to code injection via the 3MF format reader (/plugins/ThreeMFReader.py). The vulnerability arises from improper handling of the drop_to_buildplate property within 3MF files, which are ZIP archives containing the model data. When a 3MF file is loaded in Cura, the value of the drop_to_buildplate property is passed to the Python eval() function without proper sanitization, allowing an attacker to execute arbitrary code by crafting a malicious 3MF file. This vulnerability poses a significant risk as 3MF files are commonly shared via 3D model databases.

    def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]:
        """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.

        :returns: Scene node.
        """
        try:
            node_name = savitar_node.getName()
            node_id = savitar_node.getId()
        except AttributeError:
            Logger.log("e", "Outdated version of libSavitar detected! Please update to the newest version!")
            node_name = ""
            node_id = ""

        if node_name == "":
            if file_name != "":
                node_name = os.path.basename(file_name)
            else:
                node_name = "Object {}".format(node_id)

        active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate

        um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        try:
            um_node.addDecorator(ConvexHullDecorator())
        except:
            pass
        um_node.setName(node_name)
        um_node.setId(node_id)
        transformation = ThreeMFReader._createMatrixFromTransformationString(savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        if file_name:
            # The filename is used to give the user the option to reload the file if it is changed on disk
            # It is only set for the root node of the 3mf file
            mesh_builder.setFileName(file_name)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = ThreeMFReader._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder", default_stack.getId())

                # Get the definition & set it
                definition_id = ContainerTree.getInstance().machines[global_container_stack.definition.getId()].quality_definition
                um_node.callDecoration("getStack").getTop().setDefinition(definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()
            known_setting_keys = um_node.callDecoration("getStack").getAllKeys()
            for key in settings:
                setting_value = settings[key].value

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder", extruder_stack.getId())
                    else:
                        Logger.log("w", "Unable to find extruder in position %s", setting_value)
                    continue
                if key == "print_order":
                    um_node.printOrder = int(setting_value)
                    continue
                if key =="drop_to_buildplate":
                    um_node.setSetting(SceneNodeSettings.AutoDropDown, eval(setting_value))
                    continue
                if key in known_setting_keys:
                    setting_container.setProperty(key, "value", setting_value)
                else:
                    um_node.metadata[key] = settings[key]

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            if len(um_node.getAllChildren()) == 1:
                # We don't want groups of one, so move the node up one "level"
                child_node = um_node.getChildren()[0]
                # Move all the meshes of children so that toolhandles are shown in the correct place.
                if child_node.getMeshData():
                    extents = child_node.getMeshData().getExtents()
                    move_matrix = Matrix()
                    move_matrix.translate(-extents.center)
                    child_node.setMeshData(child_node.getMeshData().getTransformed(move_matrix))
                    child_node.translate(extents.center)
                parent_transformation = um_node.getLocalTransformation()
                child_transformation = child_node.getLocalTransformation()
                child_node.setTransformation(parent_transformation.multiply(child_transformation))
                um_node = cast(CuraSceneNode, um_node.getChildren()[0])
            else:
                group_decorator = GroupDecorator()
                um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node

    def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "") -> Optional[SceneNode]:
        """Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.

        :returns: Scene node.
        """
        try:
            node_name = savitar_node.getName()
            node_id = savitar_node.getId()
        except AttributeError:
            Logger.log("e", "Outdated version of libSavitar detected! Please update to the newest version!")
            node_name = ""
            node_id = ""

        if node_name == "":
            if file_name != "":
                node_name = os.path.basename(file_name)
            else:
                node_name = "Object {}".format(node_id)

        active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate

        um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
        um_node.addDecorator(BuildPlateDecorator(active_build_plate))
        try:
            um_node.addDecorator(ConvexHullDecorator())
        except:
            pass
        um_node.setName(node_name)
        um_node.setId(node_id)
        transformation = ThreeMFReader._createMatrixFromTransformationString(savitar_node.getTransformation())
        um_node.setTransformation(transformation)
        mesh_builder = MeshBuilder()

        data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)

        vertices = numpy.resize(data, (int(data.size / 3), 3))
        mesh_builder.setVertices(vertices)
        mesh_builder.calculateNormals(fast=True)
        if file_name:
            # The filename is used to give the user the option to reload the file if it is changed on disk
            # It is only set for the root node of the 3mf file
            mesh_builder.setFileName(file_name)
        mesh_data = mesh_builder.build()

        if len(mesh_data.getVertices()):
            um_node.setMeshData(mesh_data)

        for child in savitar_node.getChildren():
            child_node = ThreeMFReader._convertSavitarNodeToUMNode(child)
            if child_node:
                um_node.addChild(child_node)

        if um_node.getMeshData() is None and len(um_node.getChildren()) == 0:
            return None

        settings = savitar_node.getSettings()

        # Add the setting override decorator, so we can add settings to this node.
        if settings:
            global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()

            # Ensure the correct next container for the SettingOverride decorator is set.
            if global_container_stack:
                default_stack = ExtruderManager.getInstance().getExtruderStack(0)

                if default_stack:
                    um_node.callDecoration("setActiveExtruder", default_stack.getId())

                # Get the definition & set it
                definition_id = ContainerTree.getInstance().machines[global_container_stack.definition.getId()].quality_definition
                um_node.callDecoration("getStack").getTop().setDefinition(definition_id)

            setting_container = um_node.callDecoration("getStack").getTop()
            known_setting_keys = um_node.callDecoration("getStack").getAllKeys()
            for key in settings:
                setting_value = settings[key].value

                # Extruder_nr is a special case.
                if key == "extruder_nr":
                    extruder_stack = ExtruderManager.getInstance().getExtruderStack(int(setting_value))
                    if extruder_stack:
                        um_node.callDecoration("setActiveExtruder", extruder_stack.getId())
                    else:
                        Logger.log("w", "Unable to find extruder in position %s", setting_value)
                    continue
                if key == "print_order":
                    um_node.printOrder = int(setting_value)
                    continue
                if key =="drop_to_buildplate":
                    um_node.setSetting(SceneNodeSettings.AutoDropDown, parseBool(setting_value))
                    continue
                if key in known_setting_keys:
                    setting_container.setProperty(key, "value", setting_value)
                else:
                    um_node.metadata[key] = settings[key]

        if len(um_node.getChildren()) > 0 and um_node.getMeshData() is None:
            if len(um_node.getAllChildren()) == 1:
                # We don't want groups of one, so move the node up one "level"
                child_node = um_node.getChildren()[0]
                # Move all the meshes of children so that toolhandles are shown in the correct place.
                if child_node.getMeshData():
                    extents = child_node.getMeshData().getExtents()
                    move_matrix = Matrix()
                    move_matrix.translate(-extents.center)
                    child_node.setMeshData(child_node.getMeshData().getTransformed(move_matrix))
                    child_node.translate(extents.center)
                parent_transformation = um_node.getLocalTransformation()
                child_transformation = child_node.getLocalTransformation()
                child_node.setTransformation(parent_transformation.multiply(child_transformation))
                um_node = cast(CuraSceneNode, um_node.getChildren()[0])
            else:
                group_decorator = GroupDecorator()
                um_node.addDecorator(group_decorator)
        um_node.setSelectable(True)
        if um_node.getMeshData():
            # Assuming that all nodes with mesh data are printable objects
            # affects (auto) slicing
            sliceable_decorator = SliceableObjectDecorator()
            um_node.addDecorator(sliceable_decorator)
        return um_node

--- a/plugins/3MFReader/ThreeMFReader.py
+++ b/plugins/3MFReader/ThreeMFReader.py
@@ -87,7 +87,7 @@
                     um_node.printOrder = int(setting_value)
                     continue
                 if key =="drop_to_buildplate":
-                    um_node.setSetting(SceneNodeSettings.AutoDropDown, eval(setting_value))
+                    um_node.setSetting(SceneNodeSettings.AutoDropDown, parseBool(setting_value))
                     continue
                 if key in known_setting_keys:
                     setting_container.setProperty(key, "value", setting_value)


memos is a privacy-first, lightweight note-taking service. In memos 0.13.2, an SSRF vulnerability exists at the /o/get/httpmeta that allows unauthenticated users to enumerate the internal network and receive limited html values in json form. This vulnerability is fixed in 0.16.1.

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
	// GET /get/httpmeta?url={url} - Get website meta.
	g.GET("/get/httpmeta", GetWebsiteMetadata)

	// GET /get/image?url={url} - Get image.
	g.GET("/get/image", GetImage)
}

// GetWebsiteMetadata godoc
//
//	@Summary	Get website metadata
//	@Tags		get
//	@Produce	json
//	@Param		url	query		string			true	"Website URL"
//	@Success	200	{object}	getter.HTMLMeta	"Extracted metadata"
//	@Failure	400	{object}	nil				"Missing website url | Wrong url"
//	@Failure	406	{object}	nil				"Failed to get website meta with url: %s"
//	@Router		/o/get/GetWebsiteMetadata [GET]
func GetWebsiteMetadata(c echo.Context) error {
	urlStr := c.QueryParam("url")
	if urlStr == "" {
		return echo.NewHTTPError(http.StatusBadRequest, "Missing website url")
	}
	if _, err := url.Parse(urlStr); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "Wrong url").SetInternal(err)
	}

	htmlMeta, err := getter.GetHTMLMeta(urlStr)
	if err != nil {
		return echo.NewHTTPError(http.StatusNotAcceptable, fmt.Sprintf("Failed to get website meta with url: %s", urlStr)).SetInternal(err)
	}
	return c.JSON(http.StatusOK, htmlMeta)
}

func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
	// GET /get/image?url={url} - Get image.
	g.GET("/get/image", GetImage)
}

--- a/api/v1/http_getter.go
+++ b/api/v1/http_getter.go
@@ -1,7 +1,4 @@
 func (*APIV1Service) registerGetterPublicRoutes(g *echo.Group) {
-	// GET /get/httpmeta?url={url} - Get website meta.
-	g.GET("/get/httpmeta", GetWebsiteMetadata)
-
 	// GET /get/image?url={url} - Get image.
 	g.GET("/get/image", GetImage)
 }

--- a/api/v1/http_getter.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// GetWebsiteMetadata godoc
-//
-//	@Summary	Get website metadata
-//	@Tags		get
-//	@Produce	json
-//	@Param		url	query		string			true	"Website URL"
-//	@Success	200	{object}	getter.HTMLMeta	"Extracted metadata"
-//	@Failure	400	{object}	nil				"Missing website url | Wrong url"
-//	@Failure	406	{object}	nil				"Failed to get website meta with url: %s"
-//	@Router		/o/get/GetWebsiteMetadata [GET]
-func GetWebsiteMetadata(c echo.Context) error {
-	urlStr := c.QueryParam("url")
-	if urlStr == "" {
-		return echo.NewHTTPError(http.StatusBadRequest, "Missing website url")
-	}
-	if _, err := url.Parse(urlStr); err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, "Wrong url").SetInternal(err)
-	}
-
-	htmlMeta, err := getter.GetHTMLMeta(urlStr)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusNotAcceptable, fmt.Sprintf("Failed to get website meta with url: %s", urlStr)).SetInternal(err)
-	}
-	return c.JSON(http.StatusOK, htmlMeta)
-}


An issue was discovered in Grafana Cortex through 1.9.0. The header value X-Scope-OrgID is used to construct file paths for rules files, and if crafted to conduct directory traversal such as ae ../../sensitive/path/in/deployment pathname, then Cortex will attempt to parse a rules file at that location and include some of the contents in the error message. (Other Cortex API requests can also be sent a malicious OrgID header, e.g., tricking the ingester into writing metrics to a different location, but the effect is nuisance rather than information disclosure.)

func (t *SingleResolver) TenantIDs(ctx context.Context) ([]string, error) {
	//lint:ignore faillint wrapper around upstream method
	orgID, err := user.ExtractOrgID(ctx)
	if err != nil {
		return nil, err
	}
	return []string{orgID}, err
}

func (t *SingleResolver) TenantIDs(ctx context.Context) ([]string, error) {
	orgID, err := t.TenantID(ctx)
	if err != nil {
		return nil, err
	}
	return []string{orgID}, err
}

func containsUnsafePathSegments(id string) bool {
	// handle the relative reference to current and parent path.
	if id == "." || id == ".." {
		return true
	}

	return strings.ContainsAny(id, "\\/")
}

var errInvalidTenantID = errors.New("invalid tenant ID")

func (t *SingleResolver) TenantID(ctx context.Context) (string, error) {
	//lint:ignore faillint wrapper around upstream method
	id, err := user.ExtractOrgID(ctx)
	if err != nil {
		return "", err
	}

	if containsUnsafePathSegments(id) {
		return "", errInvalidTenantID
	}

	return id, nil
}

--- a/pkg/tenant/resolver.go
+++ b/pkg/tenant/resolver.go
@@ -1,6 +1,5 @@
 func (t *SingleResolver) TenantIDs(ctx context.Context) ([]string, error) {
-	//lint:ignore faillint wrapper around upstream method
-	orgID, err := user.ExtractOrgID(ctx)
+	orgID, err := t.TenantID(ctx)
 	if err != nil {
 		return nil, err
 	}

--- /dev/null
+++ b/pkg/tenant/resolver.go
@@ -0,0 +1,10 @@
+func containsUnsafePathSegments(id string) bool {
+	// handle the relative reference to current and parent path.
+	if id == "." || id == ".." {
+		return true
+	}
+
+	return strings.ContainsAny(id, "\\/")
+}
+
+var errInvalidTenantID = errors.New("invalid tenant ID")

--- /dev/null
+++ b/pkg/tenant/resolver.go
@@ -0,0 +1,13 @@
+func (t *SingleResolver) TenantID(ctx context.Context) (string, error) {
+	//lint:ignore faillint wrapper around upstream method
+	id, err := user.ExtractOrgID(ctx)
+	if err != nil {
+		return "", err
+	}
+
+	if containsUnsafePathSegments(id) {
+		return "", errInvalidTenantID
+	}
+
+	return id, nil
+}

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

cd /workspace/cortex
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test   -timeout 30s -run ^TestSingleResolver$ github.com/cortexproject/cortex/pkg/tenant

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

cd /workspace/cortex
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestSingleResolver|TestMultiResolver)$' github.com/cortexproject/cortex/pkg/tenant


cd-messenger through 2.7.26 is vulnerable to Arbitrary Code Execution. User input provided to the `color` argument executed by the `eval` function resulting in code execution.

  line: color => {
    if (color.length > 0) {
      try {
        eval(`cl.${color}()`); // eslint-disable-line
      }
      catch (e) {
        console.error(chalk.bgRed.bold(`Invalid Color: ${color}`));
      }
    }
  },

  line: color => {
    if (color.length > 0) {
      if (typeof cl[color] === 'function') {
        cl[color]();
      }
      else {
        console.error(chalk.bgRed.bold(`Invalid Color: ${color}`));
      }
    }
  },

--- a/src/messenger-node.js
+++ b/src/messenger-node.js
@@ -1,9 +1,9 @@
   line: color => {
     if (color.length > 0) {
-      try {
-        eval(`cl.${color}()`); // eslint-disable-line
+      if (typeof cl[color] === 'function') {
+        cl[color]();
       }
-      catch (e) {
+      else {
         console.error(chalk.bgRed.bold(`Invalid Color: ${color}`));
       }
     }

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

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


hoppscotch is an open source API development ecosystem. In versions prior to 2023.4.5 the database password is exposed in the logs when showing the database connection string. Attackers with access to read system logs will be able to elevate privilege with full access to the database. Users are advised to upgrade. There are no known workarounds for this vulnerability.

Insertion of Sensitive Information into Log File

The product writes sensitive information to a log file.

async function bootstrap() {
  console.log(`Running in production: ${process.env.PRODUCTION}`);
  console.log(`Port: ${process.env.PORT}`);
  console.log(`Database: ${process.env.DATABASE_URL}`);

  const app = await NestFactory.create(AppModule);

  app.use(
    session({
      secret: process.env.SESSION_SECRET,
    }),
  );

  // Increase fil upload limit to 50MB
  app.use(
    json({
      limit: '100mb',
    }),
  );

  if (process.env.PRODUCTION === 'false') {
    console.log('Enabling CORS with development settings');

    app.enableCors({
      origin: process.env.WHITELISTED_ORIGINS.split(','),
      credentials: true,
    });
  } else {
    console.log('Enabling CORS with production settings');

    app.enableCors({
      origin: process.env.WHITELISTED_ORIGINS.split(','),
      credentials: true,
    });
  }
  app.enableVersioning({
    type: VersioningType.URI,
  });
  app.use(cookieParser());
  await app.listen(process.env.PORT || 3170);
}

if (!process.env.GENERATE_GQL_SCHEMA) {
  bootstrap();
} else {
  emitGQLSchemaFile();
}

async function bootstrap() {
  console.log(`Running in production: ${process.env.PRODUCTION}`);
  console.log(`Port: ${process.env.PORT}`);

  const app = await NestFactory.create(AppModule);

  app.use(
    session({
      secret: process.env.SESSION_SECRET,
    }),
  );

  // Increase fil upload limit to 50MB
  app.use(
    json({
      limit: '100mb',
    }),
  );

  if (process.env.PRODUCTION === 'false') {
    console.log('Enabling CORS with development settings');

    app.enableCors({
      origin: process.env.WHITELISTED_ORIGINS.split(','),
      credentials: true,
    });
  } else {
    console.log('Enabling CORS with production settings');

    app.enableCors({
      origin: process.env.WHITELISTED_ORIGINS.split(','),
      credentials: true,
    });
  }
  app.enableVersioning({
    type: VersioningType.URI,
  });
  app.use(cookieParser());
  await app.listen(process.env.PORT || 3170);
}

if (!process.env.GENERATE_GQL_SCHEMA) {
  bootstrap();
} else {
  emitGQLSchemaFile();
}

--- a/packages/hoppscotch-backend/src/main.ts
+++ b/packages/hoppscotch-backend/src/main.ts
@@ -1,7 +1,6 @@
 async function bootstrap() {
   console.log(`Running in production: ${process.env.PRODUCTION}`);
   console.log(`Port: ${process.env.PORT}`);
-  console.log(`Database: ${process.env.DATABASE_URL}`);

   const app = await NestFactory.create(AppModule);


TensorFlow is an open source platform for machine learning. In affected versions TensorFlow's `saved_model_cli` tool is vulnerable to a code injection as it calls `eval` on user supplied strings. This can be used by attackers to run arbitrary code on the plaform where the CLI tool runs. However, given that the tool is always run manually, the impact of this is not severe. We have patched this by adding a `safe` flag which defaults to `True` and an explicit warning for users. The fix will be included in TensorFlow 2.7.0. We will also cherrypick this commit on TensorFlow 2.6.1, TensorFlow 2.5.2, and TensorFlow 2.4.4, as these are also affected and still in supported range.

def preprocess_input_exprs_arg_string(input_exprs_str):
  """Parses input arg into dictionary that maps input key to python expression.

  Parses input string in the format of 'input_key=<python expression>' into a
  dictionary that maps each input_key to its python expression.

  Args:
    input_exprs_str: A string that specifies python expression for input keys.
    Each input is separated by semicolon. For each input key:
        'input_key=<python expression>'

  Returns:
    A dictionary that maps input keys to their values.

  Raises:
    RuntimeError: An error when the given input string is in a bad format.
  """
  input_dict = {}

  for input_raw in filter(bool, input_exprs_str.split(';')):
    if '=' not in input_exprs_str:
      raise RuntimeError('--input_exprs "%s" format is incorrect. Please follow'
                         '"<input_key>=<python expression>"' % input_exprs_str)
    input_key, expr = input_raw.split('=', 1)
    # ast.literal_eval does not work with numpy expressions
    input_dict[input_key] = eval(expr)  # pylint: disable=eval-used
  return input_dict

def load_inputs_from_input_arg_string(inputs_str, input_exprs_str,
                                      input_examples_str):
  """Parses input arg strings and create inputs feed_dict.

  Parses '--inputs' string for inputs to be loaded from file, and parses
  '--input_exprs' string for inputs to be evaluated from python expression.
  '--input_examples' string for inputs to be created from tf.example feature
  dictionary list.

  Args:
    inputs_str: A string that specified where to load inputs. Each input is
        separated by semicolon.
        * For each input key:
            '<input_key>=<filename>' or
            '<input_key>=<filename>[<variable_name>]'
        * The optional 'variable_name' key will be set to None if not specified.
        * File specified by 'filename' will be loaded using numpy.load. Inputs
            can be loaded from only .npy, .npz or pickle files.
        * The "[variable_name]" key is optional depending on the input file type
            as descripted in more details below.
        When loading from a npy file, which always contains a numpy ndarray, the
        content will be directly assigned to the specified input tensor. If a
        variable_name is specified, it will be ignored and a warning will be
        issued.
        When loading from a npz zip file, user can specify which variable within
        the zip file to load for the input tensor inside the square brackets. If
        nothing is specified, this function will check that only one file is
        included in the zip and load it for the specified input tensor.
        When loading from a pickle file, if no variable_name is specified in the
        square brackets, whatever that is inside the pickle file will be passed
        to the specified input tensor, else SavedModel CLI will assume a
        dictionary is stored in the pickle file and the value corresponding to
        the variable_name will be used.
    input_exprs_str: A string that specifies python expressions for inputs.
        * In the format of: '<input_key>=<python expression>'.
        * numpy module is available as np.
    input_examples_str: A string that specifies tf.Example with dictionary.
        * In the format of: '<input_key>=<[{feature:value list}]>'

  Returns:
    A dictionary that maps input tensor keys to numpy ndarrays.

  Raises:
    RuntimeError: An error when a key is specified, but the input file contains
        multiple numpy ndarrays, none of which matches the given key.
    RuntimeError: An error when no key is specified, but the input file contains
        more than one numpy ndarrays.
  """
  tensor_key_feed_dict = {}

  inputs = preprocess_inputs_arg_string(inputs_str)
  input_exprs = preprocess_input_exprs_arg_string(input_exprs_str)
  input_examples = preprocess_input_examples_arg_string(input_examples_str)

  for input_tensor_key, (filename, variable_name) in inputs.items():
    data = np.load(file_io.FileIO(filename, mode='rb'), allow_pickle=True)  # pylint: disable=unexpected-keyword-arg

    # When a variable_name key is specified for the input file
    if variable_name:
      # if file contains a single ndarray, ignore the input name
      if isinstance(data, np.ndarray):
        logging.warn(
            'Input file %s contains a single ndarray. Name key \"%s\" ignored.'
            % (filename, variable_name))
        tensor_key_feed_dict[input_tensor_key] = data
      else:
        if variable_name in data:
          tensor_key_feed_dict[input_tensor_key] = data[variable_name]
        else:
          raise RuntimeError(
              'Input file %s does not contain variable with name \"%s\".' %
              (filename, variable_name))
    # When no key is specified for the input file.
    else:
      # Check if npz file only contains a single numpy ndarray.
      if isinstance(data, np.lib.npyio.NpzFile):
        variable_name_list = data.files
        if len(variable_name_list) != 1:
          raise RuntimeError(
              'Input file %s contains more than one ndarrays. Please specify '
              'the name of ndarray to use.' % filename)
        tensor_key_feed_dict[input_tensor_key] = data[variable_name_list[0]]
      else:
        tensor_key_feed_dict[input_tensor_key] = data

  # When input is a python expression:
  for input_tensor_key, py_expr_evaluated in input_exprs.items():
    if input_tensor_key in tensor_key_feed_dict:
      logging.warn(
          'input_key %s has been specified with both --inputs and --input_exprs'
          ' options. Value in --input_exprs will be used.' % input_tensor_key)
    tensor_key_feed_dict[input_tensor_key] = py_expr_evaluated

  # When input is a tf.Example:
  for input_tensor_key, example in input_examples.items():
    if input_tensor_key in tensor_key_feed_dict:
      logging.warn(
          'input_key %s has been specified in multiple options. Value in '
          '--input_examples will be used.' % input_tensor_key)
    tensor_key_feed_dict[input_tensor_key] = example
  return tensor_key_feed_dict

def preprocess_input_exprs_arg_string(input_exprs_str, safe=True):
  """Parses input arg into dictionary that maps input key to python expression.

  Parses input string in the format of 'input_key=<python expression>' into a
  dictionary that maps each input_key to its python expression.

  Args:
    input_exprs_str: A string that specifies python expression for input keys.
      Each input is separated by semicolon. For each input key:
        'input_key=<python expression>'
    safe: Whether to evaluate the python expression as literals or allow
      arbitrary calls (e.g. numpy usage).

  Returns:
    A dictionary that maps input keys to their values.

  Raises:
    RuntimeError: An error when the given input string is in a bad format.
  """
  input_dict = {}

  for input_raw in filter(bool, input_exprs_str.split(';')):
    if '=' not in input_exprs_str:
      raise RuntimeError('--input_exprs "%s" format is incorrect. Please follow'
                         '"<input_key>=<python expression>"' % input_exprs_str)
    input_key, expr = input_raw.split('=', 1)
    if safe:
      try:
        input_dict[input_key] = ast.literal_eval(expr)
      except:
        raise RuntimeError(
            f'Expression "{expr}" is not a valid python literal.')
    else:
      # ast.literal_eval does not work with numpy expressions
      input_dict[input_key] = eval(expr)  # pylint: disable=eval-used
  return input_dict

def load_inputs_from_input_arg_string(inputs_str, input_exprs_str,
                                      input_examples_str):
  """Parses input arg strings and create inputs feed_dict.

  Parses '--inputs' string for inputs to be loaded from file, and parses
  '--input_exprs' string for inputs to be evaluated from python expression.
  '--input_examples' string for inputs to be created from tf.example feature
  dictionary list.

  Args:
    inputs_str: A string that specified where to load inputs. Each input is
        separated by semicolon.
        * For each input key:
            '<input_key>=<filename>' or
            '<input_key>=<filename>[<variable_name>]'
        * The optional 'variable_name' key will be set to None if not specified.
        * File specified by 'filename' will be loaded using numpy.load. Inputs
            can be loaded from only .npy, .npz or pickle files.
        * The "[variable_name]" key is optional depending on the input file type
            as descripted in more details below.
        When loading from a npy file, which always contains a numpy ndarray, the
        content will be directly assigned to the specified input tensor. If a
        variable_name is specified, it will be ignored and a warning will be
        issued.
        When loading from a npz zip file, user can specify which variable within
        the zip file to load for the input tensor inside the square brackets. If
        nothing is specified, this function will check that only one file is
        included in the zip and load it for the specified input tensor.
        When loading from a pickle file, if no variable_name is specified in the
        square brackets, whatever that is inside the pickle file will be passed
        to the specified input tensor, else SavedModel CLI will assume a
        dictionary is stored in the pickle file and the value corresponding to
        the variable_name will be used.
    input_exprs_str: A string that specifies python expressions for inputs.
        * In the format of: '<input_key>=<python expression>'.
        * numpy module is available as np.
    input_examples_str: A string that specifies tf.Example with dictionary.
        * In the format of: '<input_key>=<[{feature:value list}]>'

  Returns:
    A dictionary that maps input tensor keys to numpy ndarrays.

  Raises:
    RuntimeError: An error when a key is specified, but the input file contains
        multiple numpy ndarrays, none of which matches the given key.
    RuntimeError: An error when no key is specified, but the input file contains
        more than one numpy ndarrays.
  """
  tensor_key_feed_dict = {}

  inputs = preprocess_inputs_arg_string(inputs_str)
  input_exprs = preprocess_input_exprs_arg_string(input_exprs_str, safe=False)
  input_examples = preprocess_input_examples_arg_string(input_examples_str)

  for input_tensor_key, (filename, variable_name) in inputs.items():
    data = np.load(file_io.FileIO(filename, mode='rb'), allow_pickle=True)  # pylint: disable=unexpected-keyword-arg

    # When a variable_name key is specified for the input file
    if variable_name:
      # if file contains a single ndarray, ignore the input name
      if isinstance(data, np.ndarray):
        logging.warn(
            'Input file %s contains a single ndarray. Name key \"%s\" ignored.'
            % (filename, variable_name))
        tensor_key_feed_dict[input_tensor_key] = data
      else:
        if variable_name in data:
          tensor_key_feed_dict[input_tensor_key] = data[variable_name]
        else:
          raise RuntimeError(
              'Input file %s does not contain variable with name \"%s\".' %
              (filename, variable_name))
    # When no key is specified for the input file.
    else:
      # Check if npz file only contains a single numpy ndarray.
      if isinstance(data, np.lib.npyio.NpzFile):
        variable_name_list = data.files
        if len(variable_name_list) != 1:
          raise RuntimeError(
              'Input file %s contains more than one ndarrays. Please specify '
              'the name of ndarray to use.' % filename)
        tensor_key_feed_dict[input_tensor_key] = data[variable_name_list[0]]
      else:
        tensor_key_feed_dict[input_tensor_key] = data

  # When input is a python expression:
  for input_tensor_key, py_expr_evaluated in input_exprs.items():
    if input_tensor_key in tensor_key_feed_dict:
      logging.warn(
          'input_key %s has been specified with both --inputs and --input_exprs'
          ' options. Value in --input_exprs will be used.' % input_tensor_key)
    tensor_key_feed_dict[input_tensor_key] = py_expr_evaluated

  # When input is a tf.Example:
  for input_tensor_key, example in input_examples.items():
    if input_tensor_key in tensor_key_feed_dict:
      logging.warn(
          'input_key %s has been specified in multiple options. Value in '
          '--input_examples will be used.' % input_tensor_key)
    tensor_key_feed_dict[input_tensor_key] = example
  return tensor_key_feed_dict

--- a/tensorflow/python/tools/saved_model_cli.py
+++ b/tensorflow/python/tools/saved_model_cli.py
@@ -1,4 +1,4 @@
-def preprocess_input_exprs_arg_string(input_exprs_str):
+def preprocess_input_exprs_arg_string(input_exprs_str, safe=True):
   """Parses input arg into dictionary that maps input key to python expression.

   Parses input string in the format of 'input_key=<python expression>' into a
@@ -6,8 +6,10 @@

   Args:
     input_exprs_str: A string that specifies python expression for input keys.
-    Each input is separated by semicolon. For each input key:
+      Each input is separated by semicolon. For each input key:
         'input_key=<python expression>'
+    safe: Whether to evaluate the python expression as literals or allow
+      arbitrary calls (e.g. numpy usage).

   Returns:
     A dictionary that maps input keys to their values.
@@ -22,6 +24,13 @@
       raise RuntimeError('--input_exprs "%s" format is incorrect. Please follow'
                          '"<input_key>=<python expression>"' % input_exprs_str)
     input_key, expr = input_raw.split('=', 1)
-    # ast.literal_eval does not work with numpy expressions
-    input_dict[input_key] = eval(expr)  # pylint: disable=eval-used
+    if safe:
+      try:
+        input_dict[input_key] = ast.literal_eval(expr)
+      except:
+        raise RuntimeError(
+            f'Expression "{expr}" is not a valid python literal.')
+    else:
+      # ast.literal_eval does not work with numpy expressions
+      input_dict[input_key] = eval(expr)  # pylint: disable=eval-used
   return input_dict

--- a/tensorflow/python/tools/saved_model_cli.py
+++ b/tensorflow/python/tools/saved_model_cli.py
@@ -49,7 +49,7 @@
   tensor_key_feed_dict = {}

   inputs = preprocess_inputs_arg_string(inputs_str)
-  input_exprs = preprocess_input_exprs_arg_string(input_exprs_str)
+  input_exprs = preprocess_input_exprs_arg_string(input_exprs_str, safe=False)
   input_examples = preprocess_input_examples_arg_string(input_examples_str)

   for input_tensor_key, (filename, variable_name) in inputs.items():


Cortex provides multi-tenant, long term storage for Prometheus. A local file inclusion vulnerability exists in Cortex versions 1.13.0, 1.13.1 and 1.14.0, where a malicious actor could remotely read local files as a result of parsing maliciously crafted Alertmanager configurations when submitted to the Alertmanager Set Configuration API. Only users of the Alertmanager service where `-experimental.alertmanager.enable-api` or `enable_api: true` is configured are affected. Affected Cortex users are advised to upgrade to patched versions 1.13.2 or 1.14.1. However as a workaround, Cortex administrators may reject Alertmanager configurations containing the `api_key_file` setting in the `opsgenie_configs` section before sending to the Set Alertmanager Configuration API.

func validateAlertmanagerConfig(cfg interface{}) error {
	v := reflect.ValueOf(cfg)
	t := v.Type()

	// Skip invalid, the zero value or a nil pointer (checked by zero value).
	if !v.IsValid() || v.IsZero() {
		return nil
	}

	// If the input config is a pointer then we need to get its value.
	// At this point the pointer value can't be nil.
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
		t = v.Type()
	}

	// Check if the input config is a data type for which we have a specific validation.
	// At this point the value can't be a pointer anymore.
	switch t {
	case reflect.TypeOf(config.GlobalConfig{}):
		if err := validateGlobalConfig(v.Interface().(config.GlobalConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(commoncfg.HTTPClientConfig{}):
		if err := validateReceiverHTTPConfig(v.Interface().(commoncfg.HTTPClientConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(commoncfg.TLSConfig{}):
		if err := validateReceiverTLSConfig(v.Interface().(commoncfg.TLSConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(config.SlackConfig{}):
		if err := validateSlackConfig(v.Interface().(config.SlackConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(config.VictorOpsConfig{}):
		if err := validateVictorOpsConfig(v.Interface().(config.VictorOpsConfig)); err != nil {
			return err
		}
	}

	// If the input config is a struct, recursively iterate on all fields.
	if t.Kind() == reflect.Struct {
		for i := 0; i < t.NumField(); i++ {
			field := t.Field(i)
			fieldValue := v.FieldByIndex(field.Index)

			// Skip any field value which can't be converted to interface (eg. primitive types).
			if fieldValue.CanInterface() {
				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
					return err
				}
			}
		}
	}

	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
		for i := 0; i < v.Len(); i++ {
			fieldValue := v.Index(i)

			// Skip any field value which can't be converted to interface (eg. primitive types).
			if fieldValue.CanInterface() {
				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
					return err
				}
			}
		}
	}

	if t.Kind() == reflect.Map {
		for _, key := range v.MapKeys() {
			fieldValue := v.MapIndex(key)

			// Skip any field value which can't be converted to interface (eg. primitive types).
			if fieldValue.CanInterface() {
				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func validateGlobalConfig(cfg config.GlobalConfig) error {
	if cfg.SlackAPIURLFile != "" {
		return errSlackAPIURLFileNotAllowed
	}
	return nil
}

var (
	errPasswordFileNotAllowed        = errors.New("setting password_file, bearer_token_file and credentials_file is not allowed")
	errOAuth2SecretFileNotAllowed    = errors.New("setting OAuth2 client_secret_file is not allowed")
	errTLSFileNotAllowed             = errors.New("setting TLS ca_file, cert_file and key_file is not allowed")
	errSlackAPIURLFileNotAllowed     = errors.New("setting Slack api_url_file and global slack_api_url_file is not allowed")
	errVictorOpsAPIKeyFileNotAllowed = errors.New("setting VictorOps api_key_file is not allowed")
	errOpsGenieAPIKeyFileNotAllowed  = errors.New("setting OpsGenie api_key_file is not allowed")
)

func validateAlertmanagerConfig(cfg interface{}) error {
	v := reflect.ValueOf(cfg)
	t := v.Type()

	// Skip invalid, the zero value or a nil pointer (checked by zero value).
	if !v.IsValid() || v.IsZero() {
		return nil
	}

	// If the input config is a pointer then we need to get its value.
	// At this point the pointer value can't be nil.
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
		t = v.Type()
	}

	// Check if the input config is a data type for which we have a specific validation.
	// At this point the value can't be a pointer anymore.
	switch t {
	case reflect.TypeOf(config.GlobalConfig{}):
		if err := validateGlobalConfig(v.Interface().(config.GlobalConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(commoncfg.HTTPClientConfig{}):
		if err := validateReceiverHTTPConfig(v.Interface().(commoncfg.HTTPClientConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(config.OpsGenieConfig{}):
		if err := validateOpsGenieConfig(v.Interface().(config.OpsGenieConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(commoncfg.TLSConfig{}):
		if err := validateReceiverTLSConfig(v.Interface().(commoncfg.TLSConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(config.SlackConfig{}):
		if err := validateSlackConfig(v.Interface().(config.SlackConfig)); err != nil {
			return err
		}

	case reflect.TypeOf(config.VictorOpsConfig{}):
		if err := validateVictorOpsConfig(v.Interface().(config.VictorOpsConfig)); err != nil {
			return err
		}
	}

	// If the input config is a struct, recursively iterate on all fields.
	if t.Kind() == reflect.Struct {
		for i := 0; i < t.NumField(); i++ {
			field := t.Field(i)
			fieldValue := v.FieldByIndex(field.Index)

			// Skip any field value which can't be converted to interface (eg. primitive types).
			if fieldValue.CanInterface() {
				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
					return err
				}
			}
		}
	}

	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
		for i := 0; i < v.Len(); i++ {
			fieldValue := v.Index(i)

			// Skip any field value which can't be converted to interface (eg. primitive types).
			if fieldValue.CanInterface() {
				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
					return err
				}
			}
		}
	}

	if t.Kind() == reflect.Map {
		for _, key := range v.MapKeys() {
			fieldValue := v.MapIndex(key)

			// Skip any field value which can't be converted to interface (eg. primitive types).
			if fieldValue.CanInterface() {
				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func validateGlobalConfig(cfg config.GlobalConfig) error {
	if cfg.OpsGenieAPIKeyFile != "" {
		return errOpsGenieAPIKeyFileNotAllowed
	}
	if cfg.SlackAPIURLFile != "" {
		return errSlackAPIURLFileNotAllowed
	}
	return nil
}

func validateOpsGenieConfig(cfg config.OpsGenieConfig) error {
	if cfg.APIKeyFile != "" {
		return errOpsGenieAPIKeyFileNotAllowed
	}
	return nil
}

--- a/pkg/alertmanager/api.go
+++ b/pkg/alertmanager/api.go
@@ -1,88 +1,8 @@
-func validateAlertmanagerConfig(cfg interface{}) error {
-	v := reflect.ValueOf(cfg)
-	t := v.Type()
-
-	// Skip invalid, the zero value or a nil pointer (checked by zero value).
-	if !v.IsValid() || v.IsZero() {
-		return nil
-	}
-
-	// If the input config is a pointer then we need to get its value.
-	// At this point the pointer value can't be nil.
-	if v.Kind() == reflect.Ptr {
-		v = v.Elem()
-		t = v.Type()
-	}
-
-	// Check if the input config is a data type for which we have a specific validation.
-	// At this point the value can't be a pointer anymore.
-	switch t {
-	case reflect.TypeOf(config.GlobalConfig{}):
-		if err := validateGlobalConfig(v.Interface().(config.GlobalConfig)); err != nil {
-			return err
-		}
-
-	case reflect.TypeOf(commoncfg.HTTPClientConfig{}):
-		if err := validateReceiverHTTPConfig(v.Interface().(commoncfg.HTTPClientConfig)); err != nil {
-			return err
-		}
-
-	case reflect.TypeOf(commoncfg.TLSConfig{}):
-		if err := validateReceiverTLSConfig(v.Interface().(commoncfg.TLSConfig)); err != nil {
-			return err
-		}
-
-	case reflect.TypeOf(config.SlackConfig{}):
-		if err := validateSlackConfig(v.Interface().(config.SlackConfig)); err != nil {
-			return err
-		}
-
-	case reflect.TypeOf(config.VictorOpsConfig{}):
-		if err := validateVictorOpsConfig(v.Interface().(config.VictorOpsConfig)); err != nil {
-			return err
-		}
-	}
-
-	// If the input config is a struct, recursively iterate on all fields.
-	if t.Kind() == reflect.Struct {
-		for i := 0; i < t.NumField(); i++ {
-			field := t.Field(i)
-			fieldValue := v.FieldByIndex(field.Index)
-
-			// Skip any field value which can't be converted to interface (eg. primitive types).
-			if fieldValue.CanInterface() {
-				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
-					return err
-				}
-			}
-		}
-	}
-
-	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
-		for i := 0; i < v.Len(); i++ {
-			fieldValue := v.Index(i)
-
-			// Skip any field value which can't be converted to interface (eg. primitive types).
-			if fieldValue.CanInterface() {
-				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
-					return err
-				}
-			}
-		}
-	}
-
-	if t.Kind() == reflect.Map {
-		for _, key := range v.MapKeys() {
-			fieldValue := v.MapIndex(key)
-
-			// Skip any field value which can't be converted to interface (eg. primitive types).
-			if fieldValue.CanInterface() {
-				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
-					return err
-				}
-			}
-		}
-	}
-
-	return nil
-}
+var (
+	errPasswordFileNotAllowed        = errors.New("setting password_file, bearer_token_file and credentials_file is not allowed")
+	errOAuth2SecretFileNotAllowed    = errors.New("setting OAuth2 client_secret_file is not allowed")
+	errTLSFileNotAllowed             = errors.New("setting TLS ca_file, cert_file and key_file is not allowed")
+	errSlackAPIURLFileNotAllowed     = errors.New("setting Slack api_url_file and global slack_api_url_file is not allowed")
+	errVictorOpsAPIKeyFileNotAllowed = errors.New("setting VictorOps api_key_file is not allowed")
+	errOpsGenieAPIKeyFileNotAllowed  = errors.New("setting OpsGenie api_key_file is not allowed")
+)

--- a/pkg/alertmanager/api.go
+++ b/pkg/alertmanager/api.go
@@ -1,6 +1,93 @@
-func validateGlobalConfig(cfg config.GlobalConfig) error {
-	if cfg.SlackAPIURLFile != "" {
-		return errSlackAPIURLFileNotAllowed
+func validateAlertmanagerConfig(cfg interface{}) error {
+	v := reflect.ValueOf(cfg)
+	t := v.Type()
+
+	// Skip invalid, the zero value or a nil pointer (checked by zero value).
+	if !v.IsValid() || v.IsZero() {
+		return nil
 	}
+
+	// If the input config is a pointer then we need to get its value.
+	// At this point the pointer value can't be nil.
+	if v.Kind() == reflect.Ptr {
+		v = v.Elem()
+		t = v.Type()
+	}
+
+	// Check if the input config is a data type for which we have a specific validation.
+	// At this point the value can't be a pointer anymore.
+	switch t {
+	case reflect.TypeOf(config.GlobalConfig{}):
+		if err := validateGlobalConfig(v.Interface().(config.GlobalConfig)); err != nil {
+			return err
+		}
+
+	case reflect.TypeOf(commoncfg.HTTPClientConfig{}):
+		if err := validateReceiverHTTPConfig(v.Interface().(commoncfg.HTTPClientConfig)); err != nil {
+			return err
+		}
+
+	case reflect.TypeOf(config.OpsGenieConfig{}):
+		if err := validateOpsGenieConfig(v.Interface().(config.OpsGenieConfig)); err != nil {
+			return err
+		}
+
+	case reflect.TypeOf(commoncfg.TLSConfig{}):
+		if err := validateReceiverTLSConfig(v.Interface().(commoncfg.TLSConfig)); err != nil {
+			return err
+		}
+
+	case reflect.TypeOf(config.SlackConfig{}):
+		if err := validateSlackConfig(v.Interface().(config.SlackConfig)); err != nil {
+			return err
+		}
+
+	case reflect.TypeOf(config.VictorOpsConfig{}):
+		if err := validateVictorOpsConfig(v.Interface().(config.VictorOpsConfig)); err != nil {
+			return err
+		}
+	}
+
+	// If the input config is a struct, recursively iterate on all fields.
+	if t.Kind() == reflect.Struct {
+		for i := 0; i < t.NumField(); i++ {
+			field := t.Field(i)
+			fieldValue := v.FieldByIndex(field.Index)
+
+			// Skip any field value which can't be converted to interface (eg. primitive types).
+			if fieldValue.CanInterface() {
+				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	if t.Kind() == reflect.Slice || t.Kind() == reflect.Array {
+		for i := 0; i < v.Len(); i++ {
+			fieldValue := v.Index(i)
+
+			// Skip any field value which can't be converted to interface (eg. primitive types).
+			if fieldValue.CanInterface() {
+				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	if t.Kind() == reflect.Map {
+		for _, key := range v.MapKeys() {
+			fieldValue := v.MapIndex(key)
+
+			// Skip any field value which can't be converted to interface (eg. primitive types).
+			if fieldValue.CanInterface() {
+				if err := validateAlertmanagerConfig(fieldValue.Interface()); err != nil {
+					return err
+				}
+			}
+		}
+	}
+
 	return nil
 }

--- /dev/null
+++ b/pkg/alertmanager/api.go
@@ -0,0 +1,9 @@
+func validateGlobalConfig(cfg config.GlobalConfig) error {
+	if cfg.OpsGenieAPIKeyFile != "" {
+		return errOpsGenieAPIKeyFileNotAllowed
+	}
+	if cfg.SlackAPIURLFile != "" {
+		return errSlackAPIURLFileNotAllowed
+	}
+	return nil
+}

--- /dev/null
+++ b/pkg/alertmanager/api.go
@@ -0,0 +1,6 @@
+func validateOpsGenieConfig(cfg config.OpsGenieConfig) error {
+	if cfg.APIKeyFile != "" {
+		return errOpsGenieAPIKeyFileNotAllowed
+	}
+	return nil
+}

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

cd /workspace/cortex
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestAMConfigValidationAPI$ github.com/cortexproject/cortex/pkg/alertmanager

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

cd /workspace/cortex
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestMultitenantAlertmanager_DeleteUserConfig|TestAMConfigListUserConfig|TestAMConfigValidationAPI|TestValidateAlertmanagerConfig)$' github.com/cortexproject/cortex/pkg/alertmanager


Insecure Temporary File in GitHub repository horovod/horovod prior to 0.24.0.

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.

def generate_jsrun_rankfile(settings, path=None):
    """
    Generates rankfile to use with jsrun.
    It splits the cores among the processes, which leads to best performance according to experiments.

    Args:
        settings: Settings for running jsrun.
                  Note: settings.num_proc and settings.hosts must not be None.
        path: Optional path of the rankfile.
              Note: this file will be overwritten.
    """
    cpu_per_gpu = (lsf.LSFUtils.get_num_cores() * lsf.LSFUtils.get_num_threads()) // lsf.LSFUtils.get_num_gpus()
    host_list = (x.split(':') for x in settings.hosts.split(','))

    # Verify and truncate host list if necessary
    validated_list = []
    remaining_slots = settings.num_proc
    for host, slots in host_list:
        slots = int(slots)
        if slots > lsf.LSFUtils.get_num_gpus():
            raise ValueError('Invalid host input, slot count for host \'{host}:{slots}\' is greater '
                             'than number of GPUs per host \'{gpus}\'.'.format(
                host=host, slots=slots, gpus=lsf.LSFUtils.get_num_gpus()))
        needed_slots = min(slots, remaining_slots)
        validated_list.append((host, needed_slots))
        remaining_slots -= needed_slots
        if remaining_slots == 0:
            break
    if remaining_slots != 0:
        raise ValueError('Not enough slots on the hosts to fulfill the {slots} requested.'.format(
            slots=settings.num_proc))

    # Generate rankfile
    path = tempfile.mktemp() if path is None else path
    with open(path, 'w') as tmp:
        tmp.write('overlapping_rs: allow\n')
        tmp.write('cpu_index_using: logical\n')
        rank = 0
        for host, slots in validated_list:
            cpu_val = 0
            tmp.write('\n')
            for s in range(slots):
                tmp.write('rank: {rank}: {{ hostname: {host}; cpu: {{{scpu}-{ecpu}}} ; gpu: * ; mem: * }}\n'.format(
                    rank=rank,
                    host=host,
                    scpu=cpu_val,
                    ecpu=cpu_val + cpu_per_gpu - 1
                ))
                rank += 1
                cpu_val += cpu_per_gpu
    return path

def generate_jsrun_rankfile(settings, path=None):
    """
    Generates rankfile to use with jsrun.
    It splits the cores among the processes, which leads to best performance according to experiments.

    Args:
        settings: Settings for running jsrun.
                  Note: settings.num_proc and settings.hosts must not be None.
        path: Optional path of the rankfile.
              Note: this file will be overwritten.
    """
    cpu_per_gpu = (lsf.LSFUtils.get_num_cores() * lsf.LSFUtils.get_num_threads()) // lsf.LSFUtils.get_num_gpus()
    host_list = (x.split(':') for x in settings.hosts.split(','))

    # Verify and truncate host list if necessary
    validated_list = []
    remaining_slots = settings.num_proc
    for host, slots in host_list:
        slots = int(slots)
        if slots > lsf.LSFUtils.get_num_gpus():
            raise ValueError('Invalid host input, slot count for host \'{host}:{slots}\' is greater '
                             'than number of GPUs per host \'{gpus}\'.'.format(
                host=host, slots=slots, gpus=lsf.LSFUtils.get_num_gpus()))
        needed_slots = min(slots, remaining_slots)
        validated_list.append((host, needed_slots))
        remaining_slots -= needed_slots
        if remaining_slots == 0:
            break
    if remaining_slots != 0:
        raise ValueError('Not enough slots on the hosts to fulfill the {slots} requested.'.format(
            slots=settings.num_proc))

    # Generate rankfile
    # using mkstemp here instead of insecure mktemp.
    # note that the caller is responsible for cleaning up this file
    if path is None:
        fd, path = tempfile.mkstemp()
        fd.close()

    with open(path, 'w') as tmp:
        tmp.write('overlapping_rs: allow\n')
        tmp.write('cpu_index_using: logical\n')
        rank = 0
        for host, slots in validated_list:
            cpu_val = 0
            tmp.write('\n')
            for s in range(slots):
                tmp.write('rank: {rank}: {{ hostname: {host}; cpu: {{{scpu}-{ecpu}}} ; gpu: * ; mem: * }}\n'.format(
                    rank=rank,
                    host=host,
                    scpu=cpu_val,
                    ecpu=cpu_val + cpu_per_gpu - 1
                ))
                rank += 1
                cpu_val += cpu_per_gpu
    return path

--- a/horovod/runner/js_run.py
+++ b/horovod/runner/js_run.py
@@ -31,7 +31,12 @@
             slots=settings.num_proc))

     # Generate rankfile
-    path = tempfile.mktemp() if path is None else path
+    # using mkstemp here instead of insecure mktemp.
+    # note that the caller is responsible for cleaning up this file
+    if path is None:
+        fd, path = tempfile.mkstemp()
+        fd.close()
+
     with open(path, 'w') as tmp:
         tmp.write('overlapping_rs: allow\n')
         tmp.write('cpu_index_using: logical\n')


Incorrect conversion of certain invalid paths to valid, absolute paths in Clean in path/filepath before Go 1.17.11 and Go 1.18.3 on Windows allows potential directory traversal attack.

func Clean(path string) string {
	originalPath := path
	volLen := volumeNameLen(path)
	path = path[volLen:]
	if path == "" {
		if volLen > 1 && originalPath[1] != ':' {
			// should be UNC
			return FromSlash(originalPath)
		}
		return originalPath + "."
	}
	rooted := os.IsPathSeparator(path[0])

	// Invariants:
	//	reading from path; r is index of next byte to process.
	//	writing to buf; w is index of next byte to write.
	//	dotdot is index in buf where .. must stop, either because
	//		it is the leading slash or it is a leading ../../.. prefix.
	n := len(path)
	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
	r, dotdot := 0, 0
	if rooted {
		out.append(Separator)
		r, dotdot = 1, 1
	}

	for r < n {
		switch {
		case os.IsPathSeparator(path[r]):
			// empty path element
			r++
		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
			// . element
			r++
		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
			// .. element: remove to last separator
			r += 2
			switch {
			case out.w > dotdot:
				// can backtrack
				out.w--
				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
					out.w--
				}
			case !rooted:
				// cannot backtrack, but not rooted, so append .. element.
				if out.w > 0 {
					out.append(Separator)
				}
				out.append('.')
				out.append('.')
				dotdot = out.w
			}
		default:
			// real path element.
			// add slash if needed
			if rooted && out.w != 1 || !rooted && out.w != 0 {
				out.append(Separator)
			}
			// copy element
			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
				out.append(path[r])
			}
		}
	}

	// Turn empty string into "."
	if out.w == 0 {
		out.append('.')
	}

	return FromSlash(out.string())
}

func Clean(path string) string {
	originalPath := path
	volLen := volumeNameLen(path)
	path = path[volLen:]
	if path == "" {
		if volLen > 1 && originalPath[1] != ':' {
			// should be UNC
			return FromSlash(originalPath)
		}
		return originalPath + "."
	}
	rooted := os.IsPathSeparator(path[0])

	// Invariants:
	//	reading from path; r is index of next byte to process.
	//	writing to buf; w is index of next byte to write.
	//	dotdot is index in buf where .. must stop, either because
	//		it is the leading slash or it is a leading ../../.. prefix.
	n := len(path)
	out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
	r, dotdot := 0, 0
	if rooted {
		out.append(Separator)
		r, dotdot = 1, 1
	}

	for r < n {
		switch {
		case os.IsPathSeparator(path[r]):
			// empty path element
			r++
		case path[r] == '.' && r+1 == n:
			// . element
			r++
		case path[r] == '.' && os.IsPathSeparator(path[r+1]):
			// ./ element
			r++

			for r < len(path) && os.IsPathSeparator(path[r]) {
				r++
			}
			if out.w == 0 && volumeNameLen(path[r:]) > 0 {
				// When joining prefix "." and an absolute path on Windows,
				// the prefix should not be removed.
				out.append('.')
			}
		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
			// .. element: remove to last separator
			r += 2
			switch {
			case out.w > dotdot:
				// can backtrack
				out.w--
				for out.w > dotdot && !os.IsPathSeparator(out.index(out.w)) {
					out.w--
				}
			case !rooted:
				// cannot backtrack, but not rooted, so append .. element.
				if out.w > 0 {
					out.append(Separator)
				}
				out.append('.')
				out.append('.')
				dotdot = out.w
			}
		default:
			// real path element.
			// add slash if needed
			if rooted && out.w != 1 || !rooted && out.w != 0 {
				out.append(Separator)
			}
			// copy element
			for ; r < n && !os.IsPathSeparator(path[r]); r++ {
				out.append(path[r])
			}
		}
	}

	// Turn empty string into "."
	if out.w == 0 {
		out.append('.')
	}

	return FromSlash(out.string())
}

--- a/src/path/filepath/path.go
+++ b/src/path/filepath/path.go
@@ -29,9 +29,21 @@
 		case os.IsPathSeparator(path[r]):
 			// empty path element
 			r++
-		case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
+		case path[r] == '.' && r+1 == n:
 			// . element
 			r++
+		case path[r] == '.' && os.IsPathSeparator(path[r+1]):
+			// ./ element
+			r++
+
+			for r < len(path) && os.IsPathSeparator(path[r]) {
+				r++
+			}
+			if out.w == 0 && volumeNameLen(path[r:]) > 0 {
+				// When joining prefix "." and an absolute path on Windows,
+				// the prefix should not be removed.
+				out.append('.')
+			}
 		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
 			// .. element: remove to last separator
 			r += 2


The mkdocs 1.2.2 built-in dev-server allows directory traversal using the port 8000, enabling remote exploitation to obtain :sensitive information. NOTE: the vendor has disputed this as described in https://github.com/mkdocs/mkdocs/issues/2601.] and https://github.com/nisdn/CVE-2021-40978/issues/1

    def _serve_request(self, environ, start_response):
        # https://bugs.python.org/issue16679
        # https://github.com/bottlepy/bottle/blob/f9b1849db4/bottle.py#L984
        path = environ["PATH_INFO"].encode("latin-1").decode("utf-8", "ignore")

        m = re.fullmatch(r"/livereload/([0-9]+)/[0-9]+", path)
        if m:
            epoch = int(m[1])
            start_response("200 OK", [("Content-Type", "text/plain")])

            def condition():
                return self._visible_epoch > epoch

            with self._epoch_cond:
                if not condition():
                    # Stall the browser, respond as soon as there's something new.
                    # If there's not, respond anyway after a minute.
                    self._log_poll_request(environ.get("HTTP_REFERER"), request_id=path)
                    self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)
                return [b"%d" % self._visible_epoch]

        if path == "/js/livereload.js":
            file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "livereload.js")
        elif path.startswith(self.mount_path):
            if path.endswith("/"):
                path += "index.html"
            path = path[len(self.mount_path):]
            file_path = os.path.join(self.root, path.lstrip("/"))
        elif path == "/":
            start_response("302 Found", [("Location", self.mount_path)])
            return []
        else:
            return None  # Not found

        # Wait until the ongoing rebuild (if any) finishes, so we're not serving a half-built site.
        with self._epoch_cond:
            self._epoch_cond.wait_for(lambda: self._visible_epoch == self._wanted_epoch)
            epoch = self._visible_epoch

        try:
            file = open(file_path, "rb")
        except OSError:
            return None  # Not found

        if path.endswith(".html"):
            with file:
                content = file.read()
            content = self._inject_js_into_html(content, epoch)
            file = io.BytesIO(content)
            content_length = len(content)
        else:
            content_length = os.path.getsize(file_path)

        content_type = self._guess_type(file_path)
        start_response(
            "200 OK", [("Content-Type", content_type), ("Content-Length", str(content_length))]
        )
        return wsgiref.util.FileWrapper(file)

    def _serve_request(self, environ, start_response):
        # https://bugs.python.org/issue16679
        # https://github.com/bottlepy/bottle/blob/f9b1849db4/bottle.py#L984
        path = environ["PATH_INFO"].encode("latin-1").decode("utf-8", "ignore")

        m = re.fullmatch(r"/livereload/([0-9]+)/[0-9]+", path)
        if m:
            epoch = int(m[1])
            start_response("200 OK", [("Content-Type", "text/plain")])

            def condition():
                return self._visible_epoch > epoch

            with self._epoch_cond:
                if not condition():
                    # Stall the browser, respond as soon as there's something new.
                    # If there's not, respond anyway after a minute.
                    self._log_poll_request(environ.get("HTTP_REFERER"), request_id=path)
                    self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)
                return [b"%d" % self._visible_epoch]

        if path == "/js/livereload.js":
            file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "livereload.js")
        elif path.startswith(self.mount_path):
            rel_file_path = path[len(self.mount_path):].lstrip("/")
            if path.endswith("/"):
                rel_file_path += "index.html"
            file_path = os.path.join(self.root, rel_file_path)
        elif path == "/":
            start_response("302 Found", [("Location", self.mount_path)])
            return []
        else:
            return None  # Not found

        # Wait until the ongoing rebuild (if any) finishes, so we're not serving a half-built site.
        with self._epoch_cond:
            self._epoch_cond.wait_for(lambda: self._visible_epoch == self._wanted_epoch)
            epoch = self._visible_epoch

        try:
            file = open(file_path, "rb")
        except OSError:
            if not path.endswith("/") and os.path.isfile(os.path.join(file_path, "index.html")):
                start_response("302 Found", [("Location", path + "/")])
                return []
            return None  # Not found

        if file_path.endswith(".html"):
            with file:
                content = file.read()
            content = self._inject_js_into_html(content, epoch)
            file = io.BytesIO(content)
            content_length = len(content)
        else:
            content_length = os.path.getsize(file_path)

        content_type = self._guess_type(file_path)
        start_response(
            "200 OK", [("Content-Type", content_type), ("Content-Length", str(content_length))]
        )
        return wsgiref.util.FileWrapper(file)

--- a/mkdocs/livereload/__init__.py
+++ b/mkdocs/livereload/__init__.py
@@ -22,10 +22,10 @@
         if path == "/js/livereload.js":
             file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "livereload.js")
         elif path.startswith(self.mount_path):
+            rel_file_path = path[len(self.mount_path):].lstrip("/")
             if path.endswith("/"):
-                path += "index.html"
-            path = path[len(self.mount_path):]
-            file_path = os.path.join(self.root, path.lstrip("/"))
+                rel_file_path += "index.html"
+            file_path = os.path.join(self.root, rel_file_path)
         elif path == "/":
             start_response("302 Found", [("Location", self.mount_path)])
             return []
@@ -40,9 +40,12 @@
         try:
             file = open(file_path, "rb")
         except OSError:
+            if not path.endswith("/") and os.path.isfile(os.path.join(file_path, "index.html")):
+                start_response("302 Found", [("Location", path + "/")])
+                return []
             return None  # Not found

-        if path.endswith(".html"):
+        if file_path.endswith(".html"):
             with file:
                 content = file.read()
             content = self._inject_js_into_html(content, epoch)