libseccomp-golang 0.9.0 and earlier incorrectly generates BPFs that OR multiple arguments rather than ANDing them. A process running under a restrictive seccomp filter that specified multiple syscall arguments could bypass intended access restrictions by specifying a single matching argument.

func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, cond C.scmp_cast_t) error {
	var length C.uint
	if cond != nil {
		length = 1
	} else {
		length = 0
	}

	var retCode C.int
	if exact {
		retCode = C.seccomp_rule_add_exact_array(f.filterCtx, action.toNative(), C.int(call), length, cond)
	} else {
		retCode = C.seccomp_rule_add_array(f.filterCtx, action.toNative(), C.int(call), length, cond)
	}

	if syscall.Errno(-1*retCode) == syscall.EFAULT {
		return fmt.Errorf("unrecognized syscall")
	} else if syscall.Errno(-1*retCode) == syscall.EPERM {
		return fmt.Errorf("requested action matches default action of filter")
	} else if retCode != 0 {
		return syscall.Errno(-1 * retCode)
	}

	return nil
}

func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact bool, conds []ScmpCondition) error {
	f.lock.Lock()
	defer f.lock.Unlock()

	if !f.valid {
		return errBadFilter
	}

	if len(conds) == 0 {
		if err := f.addRuleWrapper(call, action, exact, nil); err != nil {
			return err
		}
	} else {
		// We don't support conditional filtering in library version v2.1
		if !checkVersionAbove(2, 2, 1) {
			return VersionError{
				message: "conditional filtering is not supported",
				minimum: "2.2.1",
			}
		}

		for _, cond := range conds {
			cmpStruct := C.make_struct_arg_cmp(C.uint(cond.Argument), cond.Op.toNative(), C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
			defer C.free(cmpStruct)

			if err := f.addRuleWrapper(call, action, exact, C.scmp_cast_t(cmpStruct)); err != nil {
				return err
			}
		}
	}

	return nil
}

// Wrapper to create an scmp_arg_cmp struct
void*
make_struct_arg_cmp(
                    unsigned int arg,
                    int compare,
                    uint64_t a,
                    uint64_t b
                   )
{
	struct scmp_arg_cmp *s = malloc(sizeof(struct scmp_arg_cmp));

	s->arg = arg;
	s->op = compare;
	s->datum_a = a;
	s->datum_b = b;

	return s;

func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error {
	if length != 0 && cond == nil {
		return fmt.Errorf("null conditions list, but length is nonzero")
	}

	var retCode C.int
	if exact {
		retCode = C.seccomp_rule_add_exact_array(f.filterCtx, action.toNative(), C.int(call), length, cond)
	} else {
		retCode = C.seccomp_rule_add_array(f.filterCtx, action.toNative(), C.int(call), length, cond)
	}

	if syscall.Errno(-1*retCode) == syscall.EFAULT {
		return fmt.Errorf("unrecognized syscall")
	} else if syscall.Errno(-1*retCode) == syscall.EPERM {
		return fmt.Errorf("requested action matches default action of filter")
	} else if syscall.Errno(-1*retCode) == syscall.EINVAL {
		return fmt.Errorf("two checks on same syscall argument")
	} else if retCode != 0 {
		return syscall.Errno(-1 * retCode)
	}

	return nil
}

func (f *ScmpFilter) addRuleGeneric(call ScmpSyscall, action ScmpAction, exact bool, conds []ScmpCondition) error {
	f.lock.Lock()
	defer f.lock.Unlock()

	if !f.valid {
		return errBadFilter
	}

	if len(conds) == 0 {
		if err := f.addRuleWrapper(call, action, exact, 0, nil); err != nil {
			return err
		}
	} else {
		// We don't support conditional filtering in library version v2.1
		if !checkVersionAbove(2, 2, 1) {
			return VersionError{
				message: "conditional filtering is not supported",
				minimum: "2.2.1",
			}
		}

		argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
		if argsArr == nil {
			return fmt.Errorf("error allocating memory for conditions")
		}
		defer C.free(argsArr)

		for i, cond := range conds {
			C.add_struct_arg_cmp(C.scmp_cast_t(argsArr), C.uint(i),
				C.uint(cond.Argument), cond.Op.toNative(),
				C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
		}

		if err := f.addRuleWrapper(call, action, exact, C.uint(len(conds)), C.scmp_cast_t(argsArr)); err != nil {
			return err
		}
	}

	return nil
}

void* make_arg_cmp_array(unsigned int length)
{
        return calloc(length, sizeof(struct scmp_arg_cmp));
}

// Wrapper to add an scmp_arg_cmp struct to an existing arg_cmp array
void add_struct_arg_cmp(
                        struct scmp_arg_cmp* arr,
                        unsigned int pos,
                        unsigned int arg,
                        int compare,
                        uint64_t a,
                        uint64_t b
                       )
{
        arr[pos].arg = arg;
        arr[pos].op = compare;
        arr[pos].datum_a = a;
        arr[pos].datum_b = b;

        return;

--- a/seccomp_internal.go
+++ b/seccomp_internal.go
@@ -1,9 +1,6 @@
-func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, cond C.scmp_cast_t) error {
-	var length C.uint
-	if cond != nil {
-		length = 1
-	} else {
-		length = 0
+func (f *ScmpFilter) addRuleWrapper(call ScmpSyscall, action ScmpAction, exact bool, length C.uint, cond C.scmp_cast_t) error {
+	if length != 0 && cond == nil {
+		return fmt.Errorf("null conditions list, but length is nonzero")
 	}

 	var retCode C.int
@@ -17,6 +14,8 @@
 		return fmt.Errorf("unrecognized syscall")
 	} else if syscall.Errno(-1*retCode) == syscall.EPERM {
 		return fmt.Errorf("requested action matches default action of filter")
+	} else if syscall.Errno(-1*retCode) == syscall.EINVAL {
+		return fmt.Errorf("two checks on same syscall argument")
 	} else if retCode != 0 {
 		return syscall.Errno(-1 * retCode)
 	}

--- a/seccomp_internal.go
+++ b/seccomp_internal.go
@@ -7,7 +7,7 @@
 	}

 	if len(conds) == 0 {
-		if err := f.addRuleWrapper(call, action, exact, nil); err != nil {
+		if err := f.addRuleWrapper(call, action, exact, 0, nil); err != nil {
 			return err
 		}
 	} else {
@@ -19,13 +19,20 @@
 			}
 		}

-		for _, cond := range conds {
-			cmpStruct := C.make_struct_arg_cmp(C.uint(cond.Argument), cond.Op.toNative(), C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
-			defer C.free(cmpStruct)
+		argsArr := C.make_arg_cmp_array(C.uint(len(conds)))
+		if argsArr == nil {
+			return fmt.Errorf("error allocating memory for conditions")
+		}
+		defer C.free(argsArr)

-			if err := f.addRuleWrapper(call, action, exact, C.scmp_cast_t(cmpStruct)); err != nil {
-				return err
-			}
+		for i, cond := range conds {
+			C.add_struct_arg_cmp(C.scmp_cast_t(argsArr), C.uint(i),
+				C.uint(cond.Argument), cond.Op.toNative(),
+				C.uint64_t(cond.Operand1), C.uint64_t(cond.Operand2))
+		}
+
+		if err := f.addRuleWrapper(call, action, exact, C.uint(len(conds)), C.scmp_cast_t(argsArr)); err != nil {
+			return err
 		}
 	}

--- a/seccomp_internal.go
+++ b/seccomp_internal.go
@@ -1,17 +1,21 @@
-// Wrapper to create an scmp_arg_cmp struct
-void*
-make_struct_arg_cmp(
-                    unsigned int arg,
-                    int compare,
-                    uint64_t a,
-                    uint64_t b
-                   )
+void* make_arg_cmp_array(unsigned int length)
 {
-	struct scmp_arg_cmp *s = malloc(sizeof(struct scmp_arg_cmp));
+        return calloc(length, sizeof(struct scmp_arg_cmp));
+}

-	s->arg = arg;
-	s->op = compare;
-	s->datum_a = a;
-	s->datum_b = b;
+// Wrapper to add an scmp_arg_cmp struct to an existing arg_cmp array
+void add_struct_arg_cmp(
+                        struct scmp_arg_cmp* arr,
+                        unsigned int pos,
+                        unsigned int arg,
+                        int compare,
+                        uint64_t a,
+                        uint64_t b
+                       )
+{
+        arr[pos].arg = arg;
+        arr[pos].op = compare;
+        arr[pos].datum_a = a;
+        arr[pos].datum_b = b;

-	return s;
+        return;


controller/org.controller/org.controller.js in the CVE Services API 1.1.1 before 5c50baf3bda28133a3bc90b854765a64fb538304 allows an organizational administrator to transfer a user account to an arbitrary new organization, and thereby achieve unintended access within the context of that new organization.

Incorrect Authorization

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

    Object.keys(req.ctx.query).forEach(k => {
      const key = k.toLowerCase()

      if (key === 'new_username') {
        newUser.username = req.ctx.query.new_username
      } else if (key === 'org_shortname') {
        newOrgShortName = req.ctx.query.org_shortname
        changesRequirePrivilegedRole = true
      } else if (key === 'name.first') {
        newUser.name.first = req.ctx.query['name.first']
      } else if (key === 'name.last') {
        newUser.name.last = req.ctx.query['name.last']
      } else if (key === 'name.middle') {
        newUser.name.middle = req.ctx.query['name.middle']
      } else if (key === 'name.suffix') {
        newUser.name.suffix = req.ctx.query['name.suffix']
      } else if (key === 'name.surname') {
        newUser.name.surname = req.ctx.query['name.surname']
      } else if (key === 'active') {
        newUser.active = req.ctx.query.active
        changesRequirePrivilegedRole = true
      } else if (key === 'active_roles.add') {
        if (Array.isArray(req.ctx.query['active_roles.add'])) {
          req.ctx.query['active_roles.add'].forEach(r => {
            addRoles.push(r)
          })
          changesRequirePrivilegedRole = true
        }
      } else if (key === 'active_roles.remove') {
        if (Array.isArray(req.ctx.query['active_roles.remove'])) {
          req.ctx.query['active_roles.remove'].forEach(r => {
            removeRoles.push(r)
          })
          changesRequirePrivilegedRole = true
        }
      }
    })

  notAllowedToChangeOrganization () {
    const err = {}
    err.error = 'NOT_ALLOWED_TO_CHANGE_ORGANIZATION'
    err.message = 'Only the Secretariat can change the organization for a user.'
    return err
  }

async function updateUser (req, res, next) {
  try {
    const requesterShortName = req.ctx.org
    const requesterUsername = req.ctx.user
    const username = req.ctx.params.username
    const shortName = req.ctx.params.shortname
    const newUser = new User()
    let newOrgShortName
    let changesRequirePrivilegedRole // Set variable to true if protected fields are being modified
    const removeRoles = []
    const addRoles = []
    const userRepo = req.ctx.repositories.getUserRepository()
    const orgRepo = req.ctx.repositories.getOrgRepository()
    const orgUUID = await orgRepo.getOrgUUID(shortName)
    const isSecretariat = await orgRepo.isSecretariat(requesterShortName)
    const isAdmin = await userRepo.isAdmin(requesterUsername, requesterShortName) // Check if requester is Admin of the designated user's org

    if (!orgUUID) {
      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + shortName + ' organization does not exist.' })
      return res.status(404).json(error.orgDneParam(shortName))
    }

    const user = await userRepo.findOneByUserNameAndOrgUUID(username, orgUUID)
    if (!user) {
      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + username + ' does not exist for ' + shortName + ' organization.' })
      return res.status(404).json(error.userDne(username))
    }

    // check if the user is not the requester or if the requester is not a secretariat
    if ((shortName !== requesterShortName || username !== requesterUsername) && !isSecretariat) {
      // check if the requester is not and admin; if admin, the requester must be from the same org as the user
      if (!isAdmin || (isAdmin && shortName !== requesterShortName)) {
        logger.info({ uuid: req.ctx.uuid, message: 'The user can only be updated by the Secretariat, an Org admin or if the requester is the user.' })
        return res.status(403).json(error.notSameUserOrSecretariat())
      }
    }

    Object.keys(req.ctx.query).forEach(k => {
      const key = k.toLowerCase()

      if (key === 'new_username') {
        newUser.username = req.ctx.query.new_username
      } else if (key === 'org_shortname') {
        newOrgShortName = req.ctx.query.org_shortname
        changesRequirePrivilegedRole = true
        if (!isSecretariat) {
          logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + requesterUsername + ' is an Org Admin and tried to reassign the organization.' })
          return res.status(403).json(error.notAllowedToChangeOrganization())
        }
      } else if (key === 'name.first') {
        newUser.name.first = req.ctx.query['name.first']
      } else if (key === 'name.last') {
        newUser.name.last = req.ctx.query['name.last']
      } else if (key === 'name.middle') {
        newUser.name.middle = req.ctx.query['name.middle']
      } else if (key === 'name.suffix') {
        newUser.name.suffix = req.ctx.query['name.suffix']
      } else if (key === 'name.surname') {
        newUser.name.surname = req.ctx.query['name.surname']
      } else if (key === 'active') {
        newUser.active = req.ctx.query.active
        changesRequirePrivilegedRole = true
      } else if (key === 'active_roles.add') {
        if (Array.isArray(req.ctx.query['active_roles.add'])) {
          req.ctx.query['active_roles.add'].forEach(r => {
            addRoles.push(r)
          })
          changesRequirePrivilegedRole = true
        }
      } else if (key === 'active_roles.remove') {
        if (Array.isArray(req.ctx.query['active_roles.remove'])) {
          req.ctx.query['active_roles.remove'].forEach(r => {
            removeRoles.push(r)
          })
          changesRequirePrivilegedRole = true
        }
      }
    })

    // updating user's roles and org_uuid is only allowed for secretariats and org admins
    if (changesRequirePrivilegedRole && !(isAdmin || isSecretariat)) {
      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + requesterUsername + ' user is not Org Admin or Secretariat to modify these fields.' })
      return res.status(403).json(error.notOrgAdminOrSecretariat())
    }

    // check if the new org exist
    if (newOrgShortName) {
      newUser.org_UUID = await orgRepo.getOrgUUID(newOrgShortName)

      if (!newUser.org_UUID) {
        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization does not exist.' })
        return res.status(404).json(error.orgDne(newOrgShortName))
      }
    }

    let agt = setAggregateUserObj({ username: username, org_UUID: orgUUID })

    // check if org has user of same username already
    if (newUser.username && newUser.org_UUID) {
      agt = setAggregateUserObj({ username: newUser.username, org_UUID: newUser.org_UUID })
      const duplicateUsers = await userRepo.find({ org_UUID: newUser.org_UUID, username: newUser.username })
      if (duplicateUsers.length) {
        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same username.' })
        return res.status(403).json(error.duplicateUsername(newOrgShortName, newUser.username))
      }
    } else if (newUser.username) {
      agt = setAggregateUserObj({ username: newUser.username, org_UUID: orgUUID })
      const duplicateUsers = await userRepo.find({ org_UUID: orgUUID, username: newUser.username })
      if (duplicateUsers.length) {
        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + shortName + ' organization contains a user with the same username.' })
        return res.status(403).json(error.duplicateUsername(shortName, newUser.username))
      }
    } else if (newUser.org_UUID) {
      agt = setAggregateUserObj({ username: username, org_UUID: newUser.org_UUID })
      const duplicateUsers = await userRepo.find({ org_UUID: newUser.org_UUID, username: username })
      if (duplicateUsers.length) {
        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same username.' })
        return res.status(403).json(error.duplicateUsername(newOrgShortName, username))
      }
    }

    // updating the user's roles
    const roles = user.authority.active_roles

    // adding roles
    addRoles.forEach(role => {
      if (!roles.includes(role)) {
        roles.push(role)
      }
    })

    // removing roles
    removeRoles.forEach(role => {
      const index = roles.indexOf(role)

      if (index > -1) {
        roles.splice(index, 1)
      }
    })

    newUser.authority.active_roles = roles

    let result = await userRepo.updateByUserNameAndOrgUUID(username, orgUUID, newUser)
    if (result.n === 0) {
      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + username + ' does not exist for ' + shortName + ' organization.' })
      return res.status(404).json(error.userDne(username))
    }

    result = await userRepo.aggregate(agt)
    result = result.length > 0 ? result[0] : null

    const responseMessage = {
      message: username + ' was successfully updated.',
      updated: result
    }

    const payload = {
      action: 'update_user',
      change: username + ' was successfully updated.',
      req_UUID: req.ctx.uuid,
      org_UUID: await orgRepo.getOrgUUID(req.ctx.org),
      user: result
    }
    payload.user_UUID = await userRepo.getUserUUID(req.ctx.user, payload.org_UUID)
    logger.info(JSON.stringify(payload))
    return res.status(200).json(responseMessage)
  } catch (err) {
    next(err)
  }
}

--- a/src/controller/org.controller/org.controller.js
+++ b/src/controller/org.controller/org.controller.js
@@ -1,3 +1,40 @@
+async function updateUser (req, res, next) {
+  try {
+    const requesterShortName = req.ctx.org
+    const requesterUsername = req.ctx.user
+    const username = req.ctx.params.username
+    const shortName = req.ctx.params.shortname
+    const newUser = new User()
+    let newOrgShortName
+    let changesRequirePrivilegedRole // Set variable to true if protected fields are being modified
+    const removeRoles = []
+    const addRoles = []
+    const userRepo = req.ctx.repositories.getUserRepository()
+    const orgRepo = req.ctx.repositories.getOrgRepository()
+    const orgUUID = await orgRepo.getOrgUUID(shortName)
+    const isSecretariat = await orgRepo.isSecretariat(requesterShortName)
+    const isAdmin = await userRepo.isAdmin(requesterUsername, requesterShortName) // Check if requester is Admin of the designated user's org
+
+    if (!orgUUID) {
+      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + shortName + ' organization does not exist.' })
+      return res.status(404).json(error.orgDneParam(shortName))
+    }
+
+    const user = await userRepo.findOneByUserNameAndOrgUUID(username, orgUUID)
+    if (!user) {
+      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + username + ' does not exist for ' + shortName + ' organization.' })
+      return res.status(404).json(error.userDne(username))
+    }
+
+    // check if the user is not the requester or if the requester is not a secretariat
+    if ((shortName !== requesterShortName || username !== requesterUsername) && !isSecretariat) {
+      // check if the requester is not and admin; if admin, the requester must be from the same org as the user
+      if (!isAdmin || (isAdmin && shortName !== requesterShortName)) {
+        logger.info({ uuid: req.ctx.uuid, message: 'The user can only be updated by the Secretariat, an Org admin or if the requester is the user.' })
+        return res.status(403).json(error.notSameUserOrSecretariat())
+      }
+    }
+
     Object.keys(req.ctx.query).forEach(k => {
       const key = k.toLowerCase()

@@ -6,6 +43,10 @@
       } else if (key === 'org_shortname') {
         newOrgShortName = req.ctx.query.org_shortname
         changesRequirePrivilegedRole = true
+        if (!isSecretariat) {
+          logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + requesterUsername + ' is an Org Admin and tried to reassign the organization.' })
+          return res.status(403).json(error.notAllowedToChangeOrganization())
+        }
       } else if (key === 'name.first') {
         newUser.name.first = req.ctx.query['name.first']
       } else if (key === 'name.last') {
@@ -35,3 +76,95 @@
         }
       }
     })
+
+    // updating user's roles and org_uuid is only allowed for secretariats and org admins
+    if (changesRequirePrivilegedRole && !(isAdmin || isSecretariat)) {
+      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + requesterUsername + ' user is not Org Admin or Secretariat to modify these fields.' })
+      return res.status(403).json(error.notOrgAdminOrSecretariat())
+    }
+
+    // check if the new org exist
+    if (newOrgShortName) {
+      newUser.org_UUID = await orgRepo.getOrgUUID(newOrgShortName)
+
+      if (!newUser.org_UUID) {
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization does not exist.' })
+        return res.status(404).json(error.orgDne(newOrgShortName))
+      }
+    }
+
+    let agt = setAggregateUserObj({ username: username, org_UUID: orgUUID })
+
+    // check if org has user of same username already
+    if (newUser.username && newUser.org_UUID) {
+      agt = setAggregateUserObj({ username: newUser.username, org_UUID: newUser.org_UUID })
+      const duplicateUsers = await userRepo.find({ org_UUID: newUser.org_UUID, username: newUser.username })
+      if (duplicateUsers.length) {
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same username.' })
+        return res.status(403).json(error.duplicateUsername(newOrgShortName, newUser.username))
+      }
+    } else if (newUser.username) {
+      agt = setAggregateUserObj({ username: newUser.username, org_UUID: orgUUID })
+      const duplicateUsers = await userRepo.find({ org_UUID: orgUUID, username: newUser.username })
+      if (duplicateUsers.length) {
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + shortName + ' organization contains a user with the same username.' })
+        return res.status(403).json(error.duplicateUsername(shortName, newUser.username))
+      }
+    } else if (newUser.org_UUID) {
+      agt = setAggregateUserObj({ username: username, org_UUID: newUser.org_UUID })
+      const duplicateUsers = await userRepo.find({ org_UUID: newUser.org_UUID, username: username })
+      if (duplicateUsers.length) {
+        logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + newOrgShortName + ' organization contains a user with the same username.' })
+        return res.status(403).json(error.duplicateUsername(newOrgShortName, username))
+      }
+    }
+
+    // updating the user's roles
+    const roles = user.authority.active_roles
+
+    // adding roles
+    addRoles.forEach(role => {
+      if (!roles.includes(role)) {
+        roles.push(role)
+      }
+    })
+
+    // removing roles
+    removeRoles.forEach(role => {
+      const index = roles.indexOf(role)
+
+      if (index > -1) {
+        roles.splice(index, 1)
+      }
+    })
+
+    newUser.authority.active_roles = roles
+
+    let result = await userRepo.updateByUserNameAndOrgUUID(username, orgUUID, newUser)
+    if (result.n === 0) {
+      logger.info({ uuid: req.ctx.uuid, message: 'The user could not be updated because ' + username + ' does not exist for ' + shortName + ' organization.' })
+      return res.status(404).json(error.userDne(username))
+    }
+
+    result = await userRepo.aggregate(agt)
+    result = result.length > 0 ? result[0] : null
+
+    const responseMessage = {
+      message: username + ' was successfully updated.',
+      updated: result
+    }
+
+    const payload = {
+      action: 'update_user',
+      change: username + ' was successfully updated.',
+      req_UUID: req.ctx.uuid,
+      org_UUID: await orgRepo.getOrgUUID(req.ctx.org),
+      user: result
+    }
+    payload.user_UUID = await userRepo.getUserUUID(req.ctx.user, payload.org_UUID)
+    logger.info(JSON.stringify(payload))
+    return res.status(200).json(responseMessage)
+  } catch (err) {
+    next(err)
+  }
+}

--- /dev/null
+++ b/src/controller/org.controller/org.controller.js
@@ -0,0 +1,6 @@
+  notAllowedToChangeOrganization () {
+    const err = {}
+    err.error = 'NOT_ALLOWED_TO_CHANGE_ORGANIZATION'
+    err.message = 'Only the Secretariat can change the organization for a user.'
+    return err
+  }

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

cd /workspace/cve-services
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha test/unit-tests/user/userUpdateTest.js --grep "User is not updated because Org Admin is trying to change organization"

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

cd /workspace/cve-services
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha test/unit-tests/user/userUpdateTest.js


HedgeDoc (formerly known as CodiMD) is an open-source collaborative markdown editor. An attacker is able to receive arbitrary files from the file system when exporting a note to PDF. Since the code injection has to take place as note content, there fore this exploit requires the attackers ability to modify a note. This will affect all instances, which have pdf export enabled. This issue has been fixed by https://github.com/hedgedoc/hedgedoc/commit/c1789474020a6d668d616464cb2da5e90e123f65 and is available in version 1.5.0. Starting the CodiMD/HedgeDoc instance with `CMD_ALLOW_PDF_EXPORT=false` or set `"allowPDFExport": false` in config.json can mitigate this issue for those who cannot upgrade. This exploit works because while PhantomJS doesn't actually render the `file:///` references to the PDF file itself, it still uses them internally, and exfiltration is possible, and easy through JavaScript rendering. The impact is pretty bad, as the attacker is able to read the CodiMD/HedgeDoc `config.json` file as well any other files on the filesystem. Even though the suggested Docker deploy option doesn't have many interesting files itself, the `config.json` still often contains sensitive information, database credentials, and maybe OAuth secrets among other things.

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.

'use strict'

const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const { merge } = require('lodash')
const deepFreeze = require('deep-freeze')
const { Environment, Permission } = require('./enum')
const logger = require('../logger')
const { getGitCommit, getGitHubURL } = require('./utils')

const appRootPath = path.resolve(__dirname, '../../')
const env = process.env.NODE_ENV || Environment.development
const debugConfig = {
  debug: (env === Environment.development)
}

// Get version string from package.json
const { version, repository } = require(path.join(appRootPath, 'package.json'))

const commitID = getGitCommit(appRootPath)
const sourceURL = getGitHubURL(repository.url, commitID || version)
const fullversion = commitID ? `${version}-${commitID}` : version

const packageConfig = {
  version: version,
  minimumCompatibleVersion: '0.5.0',
  fullversion: fullversion,
  sourceURL: sourceURL
}

const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE ||
'config.json')
const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined

let config = require('./default')
merge(config, require('./defaultSSL'))
merge(config, require('./oldDefault'))
merge(config, debugConfig)
merge(config, packageConfig)
merge(config, fileConfig)
merge(config, require('./oldEnvironment'))
merge(config, require('./hackmdEnvironment'))
merge(config, require('./environment'))
merge(config, require('./dockerSecret'))

if (['debug', 'verbose', 'info', 'warn', 'error'].includes(config.loglevel)) {
  logger.level = config.loglevel
} else {
  logger.error('Selected loglevel %s doesn\'t exist, using default level \'debug\'. Available options: debug, verbose, info, warn, error', config.loglevel)
}

// load LDAP CA
if (config.ldap.tlsca) {
  let ca = config.ldap.tlsca.split(',')
  let caContent = []
  for (let i of ca) {
    if (fs.existsSync(i)) {
      caContent.push(fs.readFileSync(i, 'utf8'))
    }
  }
  let tlsOptions = {
    ca: caContent
  }
  config.ldap.tlsOptions = config.ldap.tlsOptions ? Object.assign(config.ldap.tlsOptions, tlsOptions) : tlsOptions
}

// Permission
config.permission = Permission
if (!config.allowAnonymous && !config.allowAnonymousEdits) {
  delete config.permission.freely
}
if (!(config.defaultPermission in config.permission)) {
  config.defaultPermission = config.permission.editable
}

// cache result, cannot change config in runtime!!!
config.isStandardHTTPsPort = (function isStandardHTTPsPort () {
  return config.useSSL && config.port === 443
})()
config.isStandardHTTPPort = (function isStandardHTTPPort () {
  return !config.useSSL && config.port === 80
})()

// cache serverURL
config.serverURL = (function getserverurl () {
  var url = ''
  if (config.domain) {
    var protocol = config.protocolUseSSL ? 'https://' : 'http://'
    url = protocol + config.domain
    if (config.urlAddPort) {
      if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) {
        url += ':' + config.port
      }
    }
  }
  if (config.urlPath) {
    url += '/' + config.urlPath
  }
  return url
})()

if (config.serverURL === '') {
  logger.warn('Neither \'domain\' nor \'CMD_DOMAIN\' is configured. This can cause issues with various components.\nHint: Make sure \'protocolUseSSL\' and \'urlAddPort\' or \'CMD_PROTOCOL_USESSL\' and \'CMD_URL_ADDPORT\' are configured properly.')
}

config.Environment = Environment

// auth method
config.isFacebookEnable = config.facebook.clientID && config.facebook.clientSecret
config.isGoogleEnable = config.google.clientID && config.google.clientSecret
config.isDropboxEnable = config.dropbox.clientID && config.dropbox.clientSecret
config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSecret
config.isEmailEnable = config.email
config.isOpenIDEnable = config.openID
config.isGitHubEnable = config.github.clientID && config.github.clientSecret
config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
config.isLDAPEnable = config.ldap.url
config.isSAMLEnable = config.saml.idpSsoUrl
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
config.isPDFExportEnable = config.allowPDFExport

// Check gitlab api version
if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') {
  logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4')
  config.gitlab.version = 'v4'
}
// If gitlab scope is api, enable snippets Export/import
config.isGitlabSnippetsEnable = (!config.gitlab.scope || config.gitlab.scope === 'api') && config.isGitLabEnable

// Only update i18n files in development setups
config.updateI18nFiles = (env === Environment.development)

// merge legacy values
let keys = Object.keys(config)
const uppercase = /[A-Z]/
for (let i = keys.length; i--;) {
  let lowercaseKey = keys[i].toLowerCase()
  // if the config contains uppercase letters
  // and a lowercase version of this setting exists
  // and the config with uppercase is not set
  // we set the new config using the old key.
  if (uppercase.test(keys[i]) &&
  config[lowercaseKey] !== undefined &&
  fileConfig[keys[i]] === undefined) {
    logger.warn('config.js contains deprecated lowercase setting for ' + keys[i] + '. Please change your config.js file to replace ' + lowercaseKey + ' with ' + keys[i])
    config[keys[i]] = config[lowercaseKey]
  }
}

// Notify users about the prefix change and inform them they use legacy prefix for environment variables
if (Object.keys(process.env).toString().indexOf('HMD_') !== -1) {
  logger.warn('Using legacy HMD prefix for environment variables. Please change your variables in future. For details see: https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs')
}

// Generate session secret if it stays on default values
if (config.sessionSecret === 'secret') {
  logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.')
  config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number
    .toString('hex') // convert to hexadecimal format
    .slice(0, config.sessionSecretLen) // return required number of characters
}

// Validate upload upload providers
if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(config.imageUploadType) === -1) {
  logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim" or "imgur". Defaulting to "filesystem"')
  config.imageUploadType = 'filesystem'
}

// figure out mime types for image uploads
switch (config.imageUploadType) {
  case 'imgur':
    config.allowedUploadMimeTypes = [
      'image/jpeg',
      'image/png',
      'image/jpg',
      'image/gif'
    ]
    break
  default:
    config.allowedUploadMimeTypes = [
      'image/jpeg',
      'image/png',
      'image/jpg',
      'image/gif',
      'image/svg+xml'
    ]
}

// generate correct path
config.sslCAPath.forEach(function (capath, i, array) {
  array[i] = path.resolve(appRootPath, capath)
})

config.sslCertPath = path.resolve(appRootPath, config.sslCertPath)
config.sslKeyPath = path.resolve(appRootPath, config.sslKeyPath)
config.dhParamPath = path.resolve(appRootPath, config.dhParamPath)
config.viewPath = path.resolve(appRootPath, config.viewPath)
config.tmpPath = path.resolve(appRootPath, config.tmpPath)
config.defaultNotePath = path.resolve(appRootPath, config.defaultNotePath)
config.docsPath = path.resolve(appRootPath, config.docsPath)
config.uploadsPath = path.resolve(appRootPath, config.uploadsPath)

// make config readonly
config = deepFreeze(config)

module.exports = config

'use strict'

const crypto = require('crypto')
const fs = require('fs')
const path = require('path')
const { merge } = require('lodash')
const deepFreeze = require('deep-freeze')
const { Environment, Permission } = require('./enum')
const logger = require('../logger')
const { getGitCommit, getGitHubURL } = require('./utils')

const appRootPath = path.resolve(__dirname, '../../')
const env = process.env.NODE_ENV || Environment.development
const debugConfig = {
  debug: (env === Environment.development)
}

// Get version string from package.json
const { version, repository } = require(path.join(appRootPath, 'package.json'))

const commitID = getGitCommit(appRootPath)
const sourceURL = getGitHubURL(repository.url, commitID || version)
const fullversion = commitID ? `${version}-${commitID}` : version

const packageConfig = {
  version: version,
  minimumCompatibleVersion: '0.5.0',
  fullversion: fullversion,
  sourceURL: sourceURL
}

const configFilePath = path.resolve(appRootPath, process.env.CMD_CONFIG_FILE ||
'config.json')
const fileConfig = fs.existsSync(configFilePath) ? require(configFilePath)[env] : undefined

let config = require('./default')
merge(config, require('./defaultSSL'))
merge(config, require('./oldDefault'))
merge(config, debugConfig)
merge(config, packageConfig)
merge(config, fileConfig)
merge(config, require('./oldEnvironment'))
merge(config, require('./hackmdEnvironment'))
merge(config, require('./environment'))
merge(config, require('./dockerSecret'))

if (['debug', 'verbose', 'info', 'warn', 'error'].includes(config.loglevel)) {
  logger.level = config.loglevel
} else {
  logger.error('Selected loglevel %s doesn\'t exist, using default level \'debug\'. Available options: debug, verbose, info, warn, error', config.loglevel)
}

// load LDAP CA
if (config.ldap.tlsca) {
  let ca = config.ldap.tlsca.split(',')
  let caContent = []
  for (let i of ca) {
    if (fs.existsSync(i)) {
      caContent.push(fs.readFileSync(i, 'utf8'))
    }
  }
  let tlsOptions = {
    ca: caContent
  }
  config.ldap.tlsOptions = config.ldap.tlsOptions ? Object.assign(config.ldap.tlsOptions, tlsOptions) : tlsOptions
}

// Permission
config.permission = Permission
if (!config.allowAnonymous && !config.allowAnonymousEdits) {
  delete config.permission.freely
}
if (!(config.defaultPermission in config.permission)) {
  config.defaultPermission = config.permission.editable
}

// cache result, cannot change config in runtime!!!
config.isStandardHTTPsPort = (function isStandardHTTPsPort () {
  return config.useSSL && config.port === 443
})()
config.isStandardHTTPPort = (function isStandardHTTPPort () {
  return !config.useSSL && config.port === 80
})()

// cache serverURL
config.serverURL = (function getserverurl () {
  var url = ''
  if (config.domain) {
    var protocol = config.protocolUseSSL ? 'https://' : 'http://'
    url = protocol + config.domain
    if (config.urlAddPort) {
      if (!config.isStandardHTTPPort || !config.isStandardHTTPsPort) {
        url += ':' + config.port
      }
    }
  }
  if (config.urlPath) {
    url += '/' + config.urlPath
  }
  return url
})()

if (config.serverURL === '') {
  logger.warn('Neither \'domain\' nor \'CMD_DOMAIN\' is configured. This can cause issues with various components.\nHint: Make sure \'protocolUseSSL\' and \'urlAddPort\' or \'CMD_PROTOCOL_USESSL\' and \'CMD_URL_ADDPORT\' are configured properly.')
}

config.Environment = Environment

// auth method
config.isFacebookEnable = config.facebook.clientID && config.facebook.clientSecret
config.isGoogleEnable = config.google.clientID && config.google.clientSecret
config.isDropboxEnable = config.dropbox.clientID && config.dropbox.clientSecret
config.isTwitterEnable = config.twitter.consumerKey && config.twitter.consumerSecret
config.isEmailEnable = config.email
config.isOpenIDEnable = config.openID
config.isGitHubEnable = config.github.clientID && config.github.clientSecret
config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
config.isLDAPEnable = config.ldap.url
config.isSAMLEnable = config.saml.idpSsoUrl
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
config.isPDFExportEnable = config.allowPDFExport

// Check gitlab api version
if (config.gitlab && config.gitlab.version !== 'v4' && config.gitlab.version !== 'v3') {
  logger.warn('config.js contains wrong version (' + config.gitlab.version + ') for gitlab api; it should be \'v3\' or \'v4\'. Defaulting to v4')
  config.gitlab.version = 'v4'
}
// If gitlab scope is api, enable snippets Export/import
config.isGitlabSnippetsEnable = (!config.gitlab.scope || config.gitlab.scope === 'api') && config.isGitLabEnable

// Only update i18n files in development setups
config.updateI18nFiles = (env === Environment.development)

// merge legacy values
let keys = Object.keys(config)
const uppercase = /[A-Z]/
for (let i = keys.length; i--;) {
  let lowercaseKey = keys[i].toLowerCase()
  // if the config contains uppercase letters
  // and a lowercase version of this setting exists
  // and the config with uppercase is not set
  // we set the new config using the old key.
  if (uppercase.test(keys[i]) &&
  config[lowercaseKey] !== undefined &&
  fileConfig[keys[i]] === undefined) {
    logger.warn('config.js contains deprecated lowercase setting for ' + keys[i] + '. Please change your config.js file to replace ' + lowercaseKey + ' with ' + keys[i])
    config[keys[i]] = config[lowercaseKey]
  }
}

// Notify users about the prefix change and inform them they use legacy prefix for environment variables
if (Object.keys(process.env).toString().indexOf('HMD_') !== -1) {
  logger.warn('Using legacy HMD prefix for environment variables. Please change your variables in future. For details see: https://github.com/codimd/server#environment-variables-will-overwrite-other-server-configs')
}

// Generate session secret if it stays on default values
if (config.sessionSecret === 'secret') {
  logger.warn('Session secret not set. Using random generated one. Please set `sessionSecret` in your config.js file. All users will be logged out.')
  config.sessionSecret = crypto.randomBytes(Math.ceil(config.sessionSecretLen / 2)) // generate crypto graphic random number
    .toString('hex') // convert to hexadecimal format
    .slice(0, config.sessionSecretLen) // return required number of characters
}

// Validate upload upload providers
if (['filesystem', 's3', 'minio', 'imgur', 'azure', 'lutim'].indexOf(config.imageUploadType) === -1) {
  logger.error('"imageuploadtype" is not correctly set. Please use "filesystem", "s3", "minio", "azure", "lutim" or "imgur". Defaulting to "filesystem"')
  config.imageUploadType = 'filesystem'
}

// figure out mime types for image uploads
switch (config.imageUploadType) {
  case 'imgur':
    config.allowedUploadMimeTypes = [
      'image/jpeg',
      'image/png',
      'image/jpg',
      'image/gif'
    ]
    break
  default:
    config.allowedUploadMimeTypes = [
      'image/jpeg',
      'image/png',
      'image/jpg',
      'image/gif',
      'image/svg+xml'
    ]
}

// Disable PDF export due to security issue
if (config.allowPDFExport) {
  config.allowPDFExport = false
  logger.warn('PDF export was disabled for this release to mitigate a critical security issue. This feature will hopefully become available again in future releases.')
}

// generate correct path
config.sslCAPath.forEach(function (capath, i, array) {
  array[i] = path.resolve(appRootPath, capath)
})

config.sslCertPath = path.resolve(appRootPath, config.sslCertPath)
config.sslKeyPath = path.resolve(appRootPath, config.sslKeyPath)
config.dhParamPath = path.resolve(appRootPath, config.dhParamPath)
config.viewPath = path.resolve(appRootPath, config.viewPath)
config.tmpPath = path.resolve(appRootPath, config.tmpPath)
config.defaultNotePath = path.resolve(appRootPath, config.defaultNotePath)
config.docsPath = path.resolve(appRootPath, config.docsPath)
config.uploadsPath = path.resolve(appRootPath, config.uploadsPath)

// make config readonly
config = deepFreeze(config)

module.exports = config

--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -189,6 +189,12 @@
     ]
 }

+// Disable PDF export due to security issue
+if (config.allowPDFExport) {
+  config.allowPDFExport = false
+  logger.warn('PDF export was disabled for this release to mitigate a critical security issue. This feature will hopefully become available again in future releases.')
+}
+
 // generate correct path
 config.sslCAPath.forEach(function (capath, i, array) {
   array[i] = path.resolve(appRootPath, capath)


Hotdog, prior to v1.0.1, did not mimic the capabilities or the SELinux label of the target JVM process. This would allow a container to gain full privileges on the host, bypassing restrictions set on the container.

Improper Privilege Management

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

func _main() error {
	state, err := hook.State()
	if err != nil {
		return err
	}

	hotpatch := exec.Command("nsenter",
		"-t", strconv.Itoa(state.Pid),
		"-m", "-n", "-i", "-u", "-p",
		filepath.Join(hotdog.ContainerDir, hotdog.HotpatchBinary))
	return hotpatch.Start()
}

func _main() error {
	state, err := hook.State()
	if err != nil {
		return err
	}
	spec, err := hook.Config(state)
	if err != nil {
		return err
	}
	if spec.Process.SelinuxLabel != "" {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()
		if err := selinux.SetExecLabel(spec.Process.SelinuxLabel); err != nil {
			return err
		}
	}
	hotpatch := exec.Command("nsenter",
		"-t", strconv.Itoa(state.Pid),
		"-m", "-n", "-i", "-u", "-p",
		filepath.Join(hotdog.ContainerDir, hotdog.HotpatchBinary))
	return hotpatch.Start()
}

--- a/cmd/hotdog-poststart-hook/main.go
+++ b/cmd/hotdog-poststart-hook/main.go
@@ -3,7 +3,17 @@
 	if err != nil {
 		return err
 	}
-
+	spec, err := hook.Config(state)
+	if err != nil {
+		return err
+	}
+	if spec.Process.SelinuxLabel != "" {
+		runtime.LockOSThread()
+		defer runtime.UnlockOSThread()
+		if err := selinux.SetExecLabel(spec.Process.SelinuxLabel); err != nil {
+			return err
+		}
+	}
 	hotpatch := exec.Command("nsenter",
 		"-t", strconv.Itoa(state.Pid),
 		"-m", "-n", "-i", "-u", "-p",


The allow_execstack plugin for setroubleshoot allows local users to execute arbitrary commands by triggering an execstack SELinux denial with a crafted filename, related to the commands.getoutput function.

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.

def is_execstack(path):
    if path[0] != "/":
        return False

    x = commands.getoutput("execstack -q %s" % path).split()
    return ( x[0]  == "X" )

def find_execstack(exe, pid):
    execstacklist = []
    for path in commands.getoutput("ldd %s" % exe).split():
        if is_execstack(path) and path not in execstacklist:
                execstacklist.append(path)
    try:
        fd = open("/proc/%s/maps" % pid , "r")
        for rec in fd.readlines():
            for path in rec.split():
                if is_execstack(path) and path not in execstacklist:
                    execstacklist.append(path)
    except IOError:
        pass

    return execstacklist

def is_execstack(path):
    if path[0] != "/":
        return False

    x = subprocess.check_output(["execstack",  "-q", path], universal_newlines=True).split()
    return ( x[0] == "X" )

def find_execstack(exe, pid):
    execstacklist = []
    for path in subprocess.check_output(["ldd", exe], universal_newlines=True).split():
        if is_execstack(path) and path not in execstacklist:
                execstacklist.append(path)
    try:
        fd = open("/proc/%s/maps" % pid , "r")
        for rec in fd.readlines():
            for path in rec.split():
                if is_execstack(path) and path not in execstacklist:
                    execstacklist.append(path)
    except IOError:
        pass

    return execstacklist

--- a/plugins/src/allow_execstack.py
+++ b/plugins/src/allow_execstack.py
@@ -2,5 +2,5 @@
     if path[0] != "/":
         return False

-    x = commands.getoutput("execstack -q %s" % path).split()
-    return ( x[0]  == "X" )
+    x = subprocess.check_output(["execstack",  "-q", path], universal_newlines=True).split()
+    return ( x[0] == "X" )

--- a/plugins/src/allow_execstack.py
+++ b/plugins/src/allow_execstack.py
@@ -1,6 +1,6 @@
 def find_execstack(exe, pid):
     execstacklist = []
-    for path in commands.getoutput("ldd %s" % exe).split():
+    for path in subprocess.check_output(["ldd", exe], universal_newlines=True).split():
         if is_execstack(path) and path not in execstacklist:
                 execstacklist.append(path)
     try:


Audiobookshelf is a self-hosted audiobook and podcast server. Starting in version 2.17.0 and prior to version 2.19.1, a flaw in the authentication bypass logic allows unauthenticated requests to match certain unanchored regex patterns in the URL. Attackers can craft URLs containing substrings like "/api/items/1/cover" in a query parameter (?r=/api/items/1/cover) to partially bypass authentication or trigger server crashes under certain routes. This could lead to information disclosure of otherwise protected data and, in some cases, a complete denial of service (server crash) if downstream code expects an authenticated user object. Version 2.19.1 contains a patch for the issue.

Improper Authentication

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

  constructor() {
    // Map of openId sessions indexed by oauth2 state-variable
    this.openIdAuthSession = new Map()
    this.ignorePattern = /\/api\/items\/[^/]+\/cover/
  }

  authNotNeeded(req) {
    return req.method === 'GET' && this.ignorePattern.test(req.originalUrl)
  }

  constructor() {
    // Map of openId sessions indexed by oauth2 state-variable
    this.openIdAuthSession = new Map()
    this.ignorePatterns = [/\/api\/items\/[^/]+\/cover/, /\/api\/authors\/[^/]+\/image/]
  }

  authNotNeeded(req) {
    return req.method === 'GET' && this.ignorePatterns.some((pattern) => pattern.test(req.originalUrl))
  }

--- a/server/Auth.js
+++ b/server/Auth.js
@@ -1,5 +1,5 @@
   constructor() {
     // Map of openId sessions indexed by oauth2 state-variable
     this.openIdAuthSession = new Map()
-    this.ignorePattern = /\/api\/items\/[^/]+\/cover/
+    this.ignorePatterns = [/\/api\/items\/[^/]+\/cover/, /\/api\/authors\/[^/]+\/image/]
   }

--- a/server/Auth.js
+++ b/server/Auth.js
@@ -1,3 +1,3 @@
   authNotNeeded(req) {
-    return req.method === 'GET' && this.ignorePattern.test(req.originalUrl)
+    return req.method === 'GET' && this.ignorePatterns.some((pattern) => pattern.test(req.originalUrl))
   }


stattic node module suffers from a Path Traversal vulnerability due to lack of validation of path, which allows a malicious user to read content of any file with known path.

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.

module.exports.listen = function (port, cb) {
    //Parse the arguments
    cb = (typeof port === "function") ? port : cb;
    port = (typeof port === "number") ? parseInt(port) : options.port;

    //Get the static files folder path
    options.folder = path.resolve(process.cwd(), options.folder);

    //Get the error file path
    options.error = path.resolve(process.cwd(), options.error);

    //Initialize the server
    let server = http.createServer(function (req, res) {
        let timeStart = Date.now();

        //Check the cors option
        if (options.cors === true) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
        }
        let pathname = url.parse(req.url).pathname;
        let localPath = path.join(options.folder, pathname);
        if (path.extname(localPath) === "") {
            //Add the index file to the local path
            localPath = path.join(localPath, "./" + path.basename(options.index));
        }

        //Reponse finish event
        res.on("finish", function () {
            console.log("" + res.statusCode + "  " + pathname + "  " + (Date.now() - timeStart) + " ms");
        });

        //Check if the file exists in this directory
        return utily.fs.isFile(localPath, function (error, exists) {
            if (error) {
                return errorPage(res, 500, "Error processing your request.");
            }
            if (exists === false) {
                return errorPage(res, 404, "File not found.");
            }

            //Write the header with the content type
            res.writeHead(200, {"Content-Type": mime.getType(localPath)});

            //Initialize the reader stream
            //let reader = fs.createReadStream(local_path, { encoding: "utf8" });
            //Remove encoding -> fixed bug reading images (jpg, png, etc...)
            let reader = fs.createReadStream(localPath);
            reader.on("data", function (data) {
                //Write the data to the response
                res.write(data);
            });
            reader.on("end", function () {
                res.end("");
            });
            reader.on("error", function (error) {
                return errorPage(res, 500, "Something went wrong...");
            })
        });
    });

    //Start server
    server.listen(port, function () {
        if (typeof cb === "function") {
            cb.call(null);
        }
        else {
            //Show the console log success
            console.log("");
            console.log("Static server listening on: " + "http://localhost:" + options.port + "");
            console.log("Reading files from: " + options.folder + "");
            console.log("");
        }
    });
};

module.exports.listen = function (port, cb) {
    //Parse the arguments
    cb = (typeof port === "function") ? port : cb;
    port = (typeof port === "number") ? parseInt(port) : options.port;

    //Get the static files folder path
    options.folder = path.resolve(process.cwd(), options.folder);

    //Get the error file path
    options.error = path.resolve(process.cwd(), options.error);

    //Initialize the server
    let server = http.createServer(function (req, res) {
        let timeStart = Date.now();

        //Check the cors option
        if (options.cors === true) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
        }
        let pathname = url.parse(req.url).pathname;
        pathname = path.normalize(pathname); //Fix path traversal
        let localPath = path.join(options.folder, pathname);
        if (path.extname(localPath) === "") {
            //Add the index file to the local path
            localPath = path.join(localPath, "./" + path.basename(options.index));
        }

        //Reponse finish event
        res.on("finish", function () {
            console.log("" + res.statusCode + "  " + pathname + "  " + (Date.now() - timeStart) + " ms");
        });

        //Check if the file exists in this directory
        return utily.fs.isFile(localPath, function (error, exists) {
            if (error) {
                return errorPage(res, 500, "Error processing your request.");
            }
            if (exists === false) {
                return errorPage(res, 404, "File not found.");
            }

            //Write the header with the content type
            res.writeHead(200, {"Content-Type": mime.getType(localPath)});

            //Initialize the reader stream
            //let reader = fs.createReadStream(local_path, { encoding: "utf8" });
            //Remove encoding -> fixed bug reading images (jpg, png, etc...)
            let reader = fs.createReadStream(localPath);
            reader.on("data", function (data) {
                //Write the data to the response
                res.write(data);
            });
            reader.on("end", function () {
                res.end("");
            });
            reader.on("error", function (error) {
                return errorPage(res, 500, "Something went wrong...");
            })
        });
    });

    //Start server
    server.listen(port, function () {
        if (typeof cb === "function") {
            cb.call(null);
        }
        else {
            //Show the console log success
            console.log("");
            console.log("Static server listening on: " + "http://localhost:" + options.port + "");
            console.log("Reading files from: " + options.folder + "");
            console.log("");
        }
    });
};

--- a/index.js
+++ b/index.js
@@ -20,6 +20,7 @@
             res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
         }
         let pathname = url.parse(req.url).pathname;
+        pathname = path.normalize(pathname); //Fix path traversal
         let localPath = path.join(options.folder, pathname);
         if (path.extname(localPath) === "") {
             //Add the index file to the local path

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

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


Scout is a web-based visualizer for VCF-files. Due to the lack of sanitization in the filename, it is possible bypass intended file extension and make users download malicious files with any extension. With malicious content injected inside the file data and users unknowingly downloading it and opening may lead to the compromise of users' devices or data. This vulnerability is fixed in 4.89.

Improper Encoding or Escaping of Output

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

def downloaded_panel_name(panel_obj, format) -> str:
    """Return a string with the file name to be downloaded

    Args:
        panel_obj(dict): scout.models.panel.gene_panel
        format(str): "pdf" or "txt"
    Returns:
        a string describing the panel
    """
    return "_".join(
        [
            panel_obj["panel_name"],
            str(panel_obj["version"]),
            dt.datetime.now().strftime(DATE_DAY_FORMATTER),
            f"scout.{format}",
        ]
    )

def downloaded_panel_name(panel_obj, format) -> str:
    """Return a string with the file name to be downloaded

    Args:
        panel_obj(dict): scout.models.panel.gene_panel
        format(str): "pdf" or "txt"
    Returns:
        a string describing the panel
    """
    sanitized_panel_id = re.sub(r"[^a-zA-Z_\-]+", "", panel_obj["panel_name"])

    return "_".join(
        [
            sanitized_panel_id,
            str(panel_obj["version"]),
            dt.datetime.now().strftime(DATE_DAY_FORMATTER),
            f"scout.{format}",
        ]
    )

--- a/scout/server/blueprints/panels/controllers.py
+++ b/scout/server/blueprints/panels/controllers.py
@@ -7,9 +7,11 @@
     Returns:
         a string describing the panel
     """
+    sanitized_panel_id = re.sub(r"[^a-zA-Z_\-]+", "", panel_obj["panel_name"])
+
     return "_".join(
         [
-            panel_obj["panel_name"],
+            sanitized_panel_id,
             str(panel_obj["version"]),
             dt.datetime.now().strftime(DATE_DAY_FORMATTER),
             f"scout.{format}",


Distribution is a toolkit to pack, ship, store, and deliver container content. Systems running registry versions 3.0.0-beta.1 through 3.0.0-rc.2 with token authentication enabled may be vulnerable to an issue in which token authentication allows an attacker to inject an untrusted signing key in a JSON web token (JWT). The issue lies in how the JSON web key (JWK) verification is performed. When a JWT contains a JWK header without a certificate chain, the code only checks if the KeyID (`kid`) matches one of the trusted keys, but doesn't verify that the actual key material matches. A fix for the issue is available at commit 5ea9aa028db65ca5665f6af2c20ecf9dc34e5fcd and expected to be a part of version 3.0.0-rc.3. There is no way to work around this issue without patching if the system requires token authentication.

Authorization Bypass Through User-Controlled Key

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

func verifyJWK(header jose.Header, verifyOpts VerifyOptions) (signingKey crypto.PublicKey, err error) {
	jwk := header.JSONWebKey
	signingKey = jwk.Key

	// Check to see if the key includes a certificate chain.
	if len(jwk.Certificates) == 0 {
		// The JWK should be one of the trusted root keys.
		if _, trusted := verifyOpts.TrustedKeys[jwk.KeyID]; !trusted {
			return nil, errors.New("untrusted JWK with no certificate chain")
		}
		// The JWK is one of the trusted keys.
		return
	}

	opts := x509.VerifyOptions{
		Roots:     verifyOpts.Roots,
		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
	}

	leaf := jwk.Certificates[0]
	if opts.Intermediates == nil {
		opts.Intermediates = x509.NewCertPool()
		for _, intermediate := range jwk.Certificates[1:] {
			opts.Intermediates.AddCert(intermediate)
		}
	}

	// TODO: this call returns certificate chains which we ignore for now, but
	// we should check them for revocations if we have the ability later.
	chains, err := leaf.Verify(opts)
	if err != nil {
		return nil, err
	}
	signingKey = getCertPubKey(chains)

	return
}

func verifyJWK(header jose.Header, verifyOpts VerifyOptions) (crypto.PublicKey, error) {
	jwk := header.JSONWebKey

	// Check to see if the key includes a certificate chain.
	if len(jwk.Certificates) == 0 {
		// The JWK should be one of the trusted root keys.
		key, trusted := verifyOpts.TrustedKeys[jwk.KeyID]
		if !trusted {
			return nil, errors.New("untrusted JWK with no certificate chain")
		}
		// The JWK is one of the trusted keys.
		return key, nil
	}

	opts := x509.VerifyOptions{
		Roots:     verifyOpts.Roots,
		KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
	}

	leaf := jwk.Certificates[0]
	if opts.Intermediates == nil {
		opts.Intermediates = x509.NewCertPool()
		for _, intermediate := range jwk.Certificates[1:] {
			opts.Intermediates.AddCert(intermediate)
		}
	}

	// TODO: this call returns certificate chains which we ignore for now, but
	// we should check them for revocations if we have the ability later.
	chains, err := leaf.Verify(opts)
	if err != nil {
		return nil, err
	}

	return getCertPubKey(chains), nil
}

--- a/registry/auth/token/token.go
+++ b/registry/auth/token/token.go
@@ -1,15 +1,15 @@
-func verifyJWK(header jose.Header, verifyOpts VerifyOptions) (signingKey crypto.PublicKey, err error) {
+func verifyJWK(header jose.Header, verifyOpts VerifyOptions) (crypto.PublicKey, error) {
 	jwk := header.JSONWebKey
-	signingKey = jwk.Key

 	// Check to see if the key includes a certificate chain.
 	if len(jwk.Certificates) == 0 {
 		// The JWK should be one of the trusted root keys.
-		if _, trusted := verifyOpts.TrustedKeys[jwk.KeyID]; !trusted {
+		key, trusted := verifyOpts.TrustedKeys[jwk.KeyID]
+		if !trusted {
 			return nil, errors.New("untrusted JWK with no certificate chain")
 		}
 		// The JWK is one of the trusted keys.
-		return
+		return key, nil
 	}

 	opts := x509.VerifyOptions{
@@ -31,7 +31,6 @@
 	if err != nil {
 		return nil, err
 	}
-	signingKey = getCertPubKey(chains)

-	return
+	return getCertPubKey(chains), nil
 }

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

cd /workspace/distribution
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^TestVerifyJWKWithTrustedKey$ github.com/distribution/distribution/v3/registry/auth/token

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

cd /workspace/distribution
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestTokenVerify|TestAccessController|TestLeeway|TestNewAccessControllerPemBlock)$' github.com/distribution/distribution/v3/registry/auth/token


The package ntesseract before 0.2.9 are vulnerable to Command Injection via lib/tesseract.js.

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 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.

  command: function (image, options) {
    // assemble tesseract command
    const command = [options.binary, image, options.output];

    if (options.l !== null) {
      command.push('-l ' + options.l);
    }

    if (options.psm !== null) {
      command.push('--psm ' + options.psm);
    }

    if (options.config !== null) {
      command.push(options.config);
    }

    const names = Object.keys(options);
    names.forEach(function (name) {
      if (name.indexOf('-') === 0) {
        command.push(name + ' ' + options[name]);
      }
    });

    return command.join(' ');
  },

  command: function (image, options) {
    if (image.startsWith('"')) {
      image = '"' + image + '"';
    }

    // assemble tesseract command
    const command = [options.binary, image, options.output];

    if (options.l !== null) {
      command.push('-l ' + options.l);
    }

    if (options.psm !== null) {
      command.push('--psm ' + options.psm);
    }

    if (options.config !== null) {
      command.push(options.config);
    }

    const names = Object.keys(options);
    names.forEach(function (name) {
      if (name.indexOf('-') === 0) {
        command.push(name + ' ' + options[name]);
      }
    });

    return command.join(' ');
  },

--- a/lib/tesseract.js
+++ b/lib/tesseract.js
@@ -1,4 +1,8 @@
   command: function (image, options) {
+    if (image.startsWith('"')) {
+      image = '"' + image + '"';
+    }
+
     // assemble tesseract command
     const command = [options.binary, image, options.output];


One of the API in Mattermost version 6.4.1 and earlier fails to properly protect the permissions, which allows the authenticated members with restricted custom admin role to bypass the restrictions and view the server logs and server config.json file contents.

func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
	const FileMime = "application/zip"
	const OutputDirectory = "support_packet"

	if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
		c.Err = model.NewAppError("generateSupportPacket", "api.restricted_system_admin", nil, "", http.StatusForbidden)
		return
	}

	// Checking to see if the user is a admin of any sort or not
	// If they are a admin, they should theoretically have access to one or more of the system console read permissions
	if !c.App.SessionHasPermissionToAny(*c.AppContext.Session(), model.SysconsoleReadPermissions) {
		c.SetPermissionError(model.SysconsoleReadPermissions...)
		return
	}

	// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
	if c.App.Srv().License() == nil {
		c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
		return
	}

	fileDatas := c.App.GenerateSupportPacket()

	// Constructing the ZIP file name as per spec (mattermost_support_packet_YYYY-MM-DD-HH-MM.zip)
	now := time.Now()
	outputZipFilename := fmt.Sprintf("mattermost_support_packet_%s.zip", now.Format("2006-01-02-03-04"))

	fileStorageBackend := c.App.FileBackend()

	// We do this incase we get concurrent requests, we will always have a unique directory.
	// This is to avoid the situation where we try to write to the same directory while we are trying to delete it (further down)
	outputDirectoryToUse := OutputDirectory + "_" + model.NewId()
	err := c.App.CreateZipFileAndAddFiles(fileStorageBackend, fileDatas, outputZipFilename, outputDirectoryToUse)
	if err != nil {
		c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_create_zip_file", nil, err.Error(), http.StatusForbidden)
		return
	}

	fileBytes, err := fileStorageBackend.ReadFile(path.Join(outputDirectoryToUse, outputZipFilename))
	defer fileStorageBackend.RemoveDirectory(outputDirectoryToUse)
	if err != nil {
		c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_read_file_from_backend", nil, err.Error(), http.StatusForbidden)
		return
	}
	fileBytesReader := bytes.NewReader(fileBytes)

	// Send the zip file back to client
	// We are able to pass 0 for content size due to the fact that Golang's serveContent (https://golang.org/src/net/http/fs.go)
	// already sets that for us
	writeFileResponse(outputZipFilename, FileMime, 0, now, *c.App.Config().ServiceSettings.WebserverMode, fileBytesReader, true, w, r)
}

func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) {
	const FileMime = "application/zip"
	const OutputDirectory = "support_packet"

	if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin {
		c.Err = model.NewAppError("generateSupportPacket", "api.restricted_system_admin", nil, "", http.StatusForbidden)
		return
	}

	// Support packet generation is limited to system admins (MM-42271).
	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
		c.SetPermissionError(model.PermissionManageSystem)
		return
	}

	// Checking to see if the server has a e10 or e20 license (this feature is only permitted for servers with licenses)
	if c.App.Srv().License() == nil {
		c.Err = model.NewAppError("Api4.generateSupportPacket", "api.no_license", nil, "", http.StatusForbidden)
		return
	}

	fileDatas := c.App.GenerateSupportPacket()

	// Constructing the ZIP file name as per spec (mattermost_support_packet_YYYY-MM-DD-HH-MM.zip)
	now := time.Now()
	outputZipFilename := fmt.Sprintf("mattermost_support_packet_%s.zip", now.Format("2006-01-02-03-04"))

	fileStorageBackend := c.App.FileBackend()

	// We do this incase we get concurrent requests, we will always have a unique directory.
	// This is to avoid the situation where we try to write to the same directory while we are trying to delete it (further down)
	outputDirectoryToUse := OutputDirectory + "_" + model.NewId()
	err := c.App.CreateZipFileAndAddFiles(fileStorageBackend, fileDatas, outputZipFilename, outputDirectoryToUse)
	if err != nil {
		c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_create_zip_file", nil, err.Error(), http.StatusForbidden)
		return
	}

	fileBytes, err := fileStorageBackend.ReadFile(path.Join(outputDirectoryToUse, outputZipFilename))
	defer fileStorageBackend.RemoveDirectory(outputDirectoryToUse)
	if err != nil {
		c.Err = model.NewAppError("Api4.generateSupportPacket", "api.unable_to_read_file_from_backend", nil, err.Error(), http.StatusForbidden)
		return
	}
	fileBytesReader := bytes.NewReader(fileBytes)

	// Send the zip file back to client
	// We are able to pass 0 for content size due to the fact that Golang's serveContent (https://golang.org/src/net/http/fs.go)
	// already sets that for us
	writeFileResponse(outputZipFilename, FileMime, 0, now, *c.App.Config().ServiceSettings.WebserverMode, fileBytesReader, true, w, r)
}

--- a/api4/system.go
+++ b/api4/system.go
@@ -7,10 +7,9 @@
 		return
 	}

-	// Checking to see if the user is a admin of any sort or not
-	// If they are a admin, they should theoretically have access to one or more of the system console read permissions
-	if !c.App.SessionHasPermissionToAny(*c.AppContext.Session(), model.SysconsoleReadPermissions) {
-		c.SetPermissionError(model.SysconsoleReadPermissions...)
+	// Support packet generation is limited to system admins (MM-42271).
+	if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSystem) {
+		c.SetPermissionError(model.PermissionManageSystem)
 		return
 	}


A vulnerability has been found in NYUCCL psiTurk up to 3.2.0 and classified as critical. This vulnerability affects unknown code of the file psiturk/experiment.py. The manipulation of the argument mode leads to improper neutralization of special elements used in a template engine. The exploit has been disclosed to the public and may be used. Upgrading to version 3.2.1 is able to address this issue. The name of the patch is 47787e15cecd66f2aa87687bf852ae0194a4335f. It is recommended to upgrade the affected component. The identifier of this vulnerability is VDB-219676.

def advertisement():
    """
    This is the url we give for the ad for our 'external question'.  The ad has
    to display two different things: This page will be called from within
    mechanical turk, with url arguments hitId, assignmentId, and workerId.
    If the worker has not yet accepted the hit:
        These arguments will have null values, we should just show an ad for
        the experiment.
    If the worker has accepted the hit:
        These arguments will have appropriate values and we should enter the
        person in the database and provide a link to the experiment popup.
    """
    user_agent_string = request.user_agent.string
    user_agent_obj = user_agents.parse(user_agent_string)
    browser_ok = True
    browser_exclude_rule = CONFIG.get('Task Parameters', 'browser_exclude_rule')
    for rule in browser_exclude_rule.split(','):
        myrule = rule.strip()
        if myrule in ["mobile", "tablet", "touchcapable", "pc", "bot"]:
            if (myrule == "mobile" and user_agent_obj.is_mobile) or\
               (myrule == "tablet" and user_agent_obj.is_tablet) or\
               (myrule == "touchcapable" and user_agent_obj.is_touch_capable) or\
               (myrule == "pc" and user_agent_obj.is_pc) or\
               (myrule == "bot" and user_agent_obj.is_bot):
                browser_ok = False
        elif myrule == "Safari" or myrule == "safari":
            if "Chrome" in user_agent_string and "Safari" in user_agent_string:
                pass
            elif "Safari" in user_agent_string:
                browser_ok = False
        elif myrule in user_agent_string:
            browser_ok = False

    if not browser_ok:
        # Handler for IE users if IE is not supported.
        raise ExperimentError('browser_type_not_allowed')

    if not ('hitId' in request.args and 'assignmentId' in request.args):
        raise ExperimentError('hit_assign_worker_id_not_set_in_mturk')
    hit_id = request.args['hitId']
    assignment_id = request.args['assignmentId']
    mode = request.args['mode']
    if hit_id[:5] == "debug":
        debug_mode = True
    else:
        debug_mode = False
    already_in_db = False
    if 'workerId' in request.args:
        worker_id = request.args['workerId']
        # First check if this workerId has completed the task before (v1).
        nrecords = Participant.query.\
            filter(Participant.assignmentid != assignment_id).\
            filter(Participant.workerid == worker_id).\
            count()

        if nrecords > 0:  # Already completed task
            already_in_db = True
    else:  # If worker has not accepted the hit
        worker_id = None
    try:
        part = Participant.query.\
            filter(Participant.hitid == hit_id).\
            filter(Participant.assignmentid == assignment_id).\
            filter(Participant.workerid == worker_id).\
            one()
        status = part.status
    except exc.SQLAlchemyError:
        status = None

    allow_repeats = CONFIG.getboolean('Task Parameters', 'allow_repeats')
    if (status == STARTED or status == QUITEARLY) and not debug_mode:
        # Once participants have finished the instructions, we do not allow
        # them to start the task again.
        raise ExperimentError('already_started_exp_mturk')
    elif status == COMPLETED or (status == SUBMITTED and not already_in_db):
        # 'or status == SUBMITTED' because we suspect that sometimes the post
        # to mturk fails after we've set status to SUBMITTED, so really they
        # have not successfully submitted. This gives another chance for the
        # submit to work.

        # They've finished the experiment but haven't successfully submitted the HIT
        # yet.
        return render_template(
            'thanks-mturksubmit.html',
            using_sandbox=(mode == "sandbox"),
            hitid=hit_id,
            assignmentid=assignment_id,
            workerid=worker_id
        )
    elif already_in_db and not (debug_mode or allow_repeats):
        raise ExperimentError('already_did_exp_hit')
    elif status == ALLOCATED or not status or debug_mode:
        # Participant has not yet agreed to the consent. They might not
        # even have accepted the HIT.
        with open('templates/ad.html', 'r') as temp_file:
            ad_string = temp_file.read()
        ad_string = insert_mode(ad_string, mode)
        return render_template_string(
            ad_string,
            hitid=hit_id,
            assignmentid=assignment_id,
            workerid=worker_id
        )
    else:
        raise ExperimentError('status_incorrectly_set')

def give_consent():
    """
    Serves up the consent in the popup window.
    """
    if not ('hitId' in request.args and 'assignmentId' in request.args and
            'workerId' in request.args):
        raise ExperimentError('hit_assign_worker_id_not_set_in_consent')
    hit_id = request.args['hitId']
    assignment_id = request.args['assignmentId']
    worker_id = request.args['workerId']
    mode = request.args['mode']
    with open('templates/consent.html', 'r') as temp_file:
        consent_string = temp_file.read()
    consent_string = insert_mode(consent_string, mode)
    return render_template_string(
        consent_string,
        hitid=hit_id,
        assignmentid=assignment_id,
        workerid=worker_id
    )

def insert_mode(page_html, mode):
    """ Insert mode """
    page_html = page_html
    match_found = False
    matches = re.finditer('workerId={{ workerid }}', page_html)
    match = None
    for match in matches:
        match_found = True
    if match_found:
        new_html = page_html[:match.end()] + "&mode=" + mode +\
            page_html[match.end():]
        return new_html
    else:
        raise ExperimentError("insert_mode_failed")

def advertisement():
    """
    This is the url we give for the ad for our 'external question'.  The ad has
    to display two different things: This page will be called from within
    mechanical turk, with url arguments hitId, assignmentId, and workerId.
    If the worker has not yet accepted the hit:
        These arguments will have null values, we should just show an ad for
        the experiment.
    If the worker has accepted the hit:
        These arguments will have appropriate values and we should enter the
        person in the database and provide a link to the experiment popup.
    """
    user_agent_string = request.user_agent.string
    user_agent_obj = user_agents.parse(user_agent_string)
    browser_ok = True
    browser_exclude_rule = CONFIG.get('Task Parameters', 'browser_exclude_rule')
    for rule in browser_exclude_rule.split(','):
        myrule = rule.strip()
        if myrule in ["mobile", "tablet", "touchcapable", "pc", "bot"]:
            if (myrule == "mobile" and user_agent_obj.is_mobile) or\
               (myrule == "tablet" and user_agent_obj.is_tablet) or\
               (myrule == "touchcapable" and user_agent_obj.is_touch_capable) or\
               (myrule == "pc" and user_agent_obj.is_pc) or\
               (myrule == "bot" and user_agent_obj.is_bot):
                browser_ok = False
        elif myrule == "Safari" or myrule == "safari":
            if "Chrome" in user_agent_string and "Safari" in user_agent_string:
                pass
            elif "Safari" in user_agent_string:
                browser_ok = False
        elif myrule in user_agent_string:
            browser_ok = False

    if not browser_ok:
        # Handler for IE users if IE is not supported.
        raise ExperimentError('browser_type_not_allowed')

    if not ('hitId' in request.args and 'assignmentId' in request.args):
        raise ExperimentError('hit_assign_worker_id_not_set_in_mturk')
    hit_id = request.args['hitId']
    assignment_id = request.args['assignmentId']
    mode = request.args['mode']
    if hit_id[:5] == "debug":
        debug_mode = True
    else:
        debug_mode = False
    already_in_db = False
    if 'workerId' in request.args:
        worker_id = request.args['workerId']
        # First check if this workerId has completed the task before (v1).
        nrecords = Participant.query.\
            filter(Participant.assignmentid != assignment_id).\
            filter(Participant.workerid == worker_id).\
            count()

        if nrecords > 0:  # Already completed task
            already_in_db = True
    else:  # If worker has not accepted the hit
        worker_id = None
    try:
        part = Participant.query.\
            filter(Participant.hitid == hit_id).\
            filter(Participant.assignmentid == assignment_id).\
            filter(Participant.workerid == worker_id).\
            one()
        status = part.status
    except exc.SQLAlchemyError:
        status = None

    allow_repeats = CONFIG.getboolean('Task Parameters', 'allow_repeats')
    if (status == STARTED or status == QUITEARLY) and not debug_mode:
        # Once participants have finished the instructions, we do not allow
        # them to start the task again.
        raise ExperimentError('already_started_exp_mturk')
    elif status == COMPLETED or (status == SUBMITTED and not already_in_db):
        # 'or status == SUBMITTED' because we suspect that sometimes the post
        # to mturk fails after we've set status to SUBMITTED, so really they
        # have not successfully submitted. This gives another chance for the
        # submit to work.

        # They've finished the experiment but haven't successfully submitted the HIT
        # yet.
        return render_template(
            'thanks-mturksubmit.html',
            using_sandbox=(mode == "sandbox"),
            hitid=hit_id,
            assignmentid=assignment_id,
            workerid=worker_id
        )
    elif already_in_db and not (debug_mode or allow_repeats):
        raise ExperimentError('already_did_exp_hit')
    elif status == ALLOCATED or not status or debug_mode:
        # Participant has not yet agreed to the consent. They might not
        # even have accepted the HIT.
        with open('templates/ad.html', 'r') as temp_file:
            ad_string = temp_file.read()
        ad_string = insert_mode(ad_string)
        return render_template_string(
            ad_string,
            mode=mode,
            hitid=hit_id,
            assignmentid=assignment_id,
            workerid=worker_id
        )
    else:
        raise ExperimentError('status_incorrectly_set')

def give_consent():
    """
    Serves up the consent in the popup window.
    """
    if not ('hitId' in request.args and 'assignmentId' in request.args and
            'workerId' in request.args):
        raise ExperimentError('hit_assign_worker_id_not_set_in_consent')
    hit_id = request.args['hitId']
    assignment_id = request.args['assignmentId']
    worker_id = request.args['workerId']
    mode = request.args['mode']
    with open('templates/consent.html', 'r') as temp_file:
        consent_string = temp_file.read()
    consent_string = insert_mode(consent_string)
    return render_template_string(
        consent_string,
        mode=mode,
        hitid=hit_id,
        assignmentid=assignment_id,
        workerid=worker_id
    )

def insert_mode(page_html):
    """ Insert mode """
    page_html = page_html
    match_found = False
    matches = re.finditer('workerId={{ workerid }}', page_html)
    match = None
    for match in matches:
        match_found = True
    if match_found:
        new_html = page_html[:match.end()] + '&mode={{ mode }}' +\
            page_html[match.end():]
        return new_html
    else:
        raise ExperimentError("insert_mode_failed")

--- a/psiturk/experiment.py
+++ b/psiturk/experiment.py
@@ -94,9 +94,10 @@
         # even have accepted the HIT.
         with open('templates/ad.html', 'r') as temp_file:
             ad_string = temp_file.read()
-        ad_string = insert_mode(ad_string, mode)
+        ad_string = insert_mode(ad_string)
         return render_template_string(
             ad_string,
+            mode=mode,
             hitid=hit_id,
             assignmentid=assignment_id,
             workerid=worker_id

--- a/psiturk/experiment.py
+++ b/psiturk/experiment.py
@@ -1,3 +1,4 @@
+def give_consent():
     """
     Serves up the consent in the popup window.
     """
@@ -10,9 +11,10 @@
     mode = request.args['mode']
     with open('templates/consent.html', 'r') as temp_file:
         consent_string = temp_file.read()
-    consent_string = insert_mode(consent_string, mode)
+    consent_string = insert_mode(consent_string)
     return render_template_string(
         consent_string,
+        mode=mode,
         hitid=hit_id,
         assignmentid=assignment_id,
         workerid=worker_id

--- a/psiturk/experiment.py
+++ b/psiturk/experiment.py
@@ -1,4 +1,4 @@
-def insert_mode(page_html, mode):
+def insert_mode(page_html):
     """ Insert mode """
     page_html = page_html
     match_found = False
@@ -7,7 +7,7 @@
     for match in matches:
         match_found = True
     if match_found:
-        new_html = page_html[:match.end()] + "&mode=" + mode +\
+        new_html = page_html[:match.end()] + '&mode={{ mode }}' +\
             page_html[match.end():]
         return new_html
     else:

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

cd /workspace/psiTurk
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
PYTHONPATH=. /workspace/PoC_env/CVE-2021-4315/bin/python -m pytest tests/test_psiturk.py -k "test_insert_mode" --override-ini="addopts=" -p no:warning --disable-warnings

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

cd /workspace/psiTurk
git apply --whitespace=nowarn /workspace/fix.patch
PYTHONPATH=. /workspace/PoC_env/CVE-2021-4315/bin/python -m pytest tests/test_psiturk.py -k "not test_insert_mode" --override-ini="addopts=" -p no:warning --disable-warnings


Exposure of Sensitive Information to an Unauthorized Actor in GitHub repository ionicabizau/parse-url prior to 7.0.0.

The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.

function parseUrl(url, normalize = false) {
    if (typeof url !== "string" || !url.trim()) {
        throw new Error("Invalid url.")
    }
    if (normalize) {
        if (typeof normalize !== "object") {
            normalize = {
                stripHash: false
            }
        }
        url = normalizeUrl(url, normalize)
    }
    const parsed = parsePath(url)
    return parsed;
}

const parseUrl = (url, normalize = false) => {

    // Constants
    const GIT_RE = /((git@|http(s)?:\/\/)([\w\.@]+)(\/|:))(([\~,\w,\-,\_,\/]+)(.git){0,1}((\/){0,1}))/

    if (typeof url !== "string" || !url.trim()) {
        throw new Error("Invalid url.")
    }

    if (normalize) {
        if (typeof normalize !== "object") {
            normalize = {
                stripHash: false
            }
        }
        url = normalizeUrl(url, normalize)
    }

    const parsed = parsePath(url)

    // Potential git-ssh urls
    if (parsed.protocol === "file") {
        const matched  = parsed.href.match(GIT_RE)
        if (matched) {
            parsed.protocols = ["ssh"]
            parsed.protocol = "ssh"
            parsed.resource = matched[4]
            parsed.user = "git"
            parsed.pathname = `/${matched[6]}`
        }
    }

    return parsed;
}

--- a/lib/index.js
+++ b/lib/index.js
@@ -1,7 +1,12 @@
-function parseUrl(url, normalize = false) {
+const parseUrl = (url, normalize = false) => {
+
+    // Constants
+    const GIT_RE = /((git@|http(s)?:\/\/)([\w\.@]+)(\/|:))(([\~,\w,\-,\_,\/]+)(.git){0,1}((\/){0,1}))/
+
     if (typeof url !== "string" || !url.trim()) {
         throw new Error("Invalid url.")
     }
+
     if (normalize) {
         if (typeof normalize !== "object") {
             normalize = {
@@ -10,6 +15,20 @@
         }
         url = normalizeUrl(url, normalize)
     }
+
     const parsed = parsePath(url)
+
+    // Potential git-ssh urls
+    if (parsed.protocol === "file") {
+        const matched  = parsed.href.match(GIT_RE)
+        if (matched) {
+            parsed.protocols = ["ssh"]
+            parsed.protocol = "ssh"
+            parsed.resource = matched[4]
+            parsed.user = "git"
+            parsed.pathname = `/${matched[6]}`
+        }
+    }
+
     return parsed;
 }

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

cd /workspace/parse-url
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
node test


mprweb is a hosting platform for the makedeb Package Repository. Email addresses were found to not have been hidden, even if a user had clicked the `Hide Email Address` checkbox on their account page, or during signup. This could lead to an account's email being leaked, which may be problematic if your email needs to remain private for any reason. Users hosting their own mprweb instance will need to upgrade to the latest commit to get this fixed. Users on the official instance will already have this issue fixed.

async def account_register_post(
    request: Request,
    U: str = Form(default=str()),  # Username
    E: str = Form(default=str()),  # Email
    H: str = Form(default=False),  # Hide Email
    BE: str = Form(default=None),  # Backup Email
    R: str = Form(default=""),  # Real Name
    HP: str = Form(default=None),  # Homepage
    I: str = Form(default=None),  # IRC Nick # noqa: E741
    K: str = Form(default=None),  # PGP Key
    L: str = Form(default=aurweb.config.get("options", "default_lang")),
    TZ: str = Form(default=aurweb.config.get("options", "default_timezone")),
    PK: str = Form(default=None),  # SSH PubKey
    CN: bool = Form(default=False),
    UN: bool = Form(default=False),
    ON: bool = Form(default=False),
    captcha: str = Form(default=None),
    captcha_salt: str = Form(...),
):
    context = await make_variable_context(request, "Register")
    args = dict(await request.form())

    context = make_account_form_context(context, request, None, args)
    ok, errors = process_account_form(request, request.user, args)
    if not ok:
        # If the field values given do not meet the requirements,
        # return HTTP 400 with an error.
        context["errors"] = errors
        return render_template(
            request, "register.html", context, status_code=HTTPStatus.BAD_REQUEST
        )

    if not captcha:
        context["errors"] = ["The CAPTCHA is missing."]
        return render_template(
            request, "register.html", context, status_code=HTTPStatus.BAD_REQUEST
        )

    # Create a user with no password with a resetkey, then send
    # an email off about it.
    resetkey = generate_resetkey()

    # By default, we grab the User account type to associate with.
    atype = db.query(
        models.AccountType, models.AccountType.AccountType == "User"
    ).first()

    # Create a user given all parameters available.
    with db.begin():
        user = db.create(
            models.User,
            Username=U,
            Email=E,
            HideEmail=H,
            BackupEmail=BE,
            RealName=R,
            Homepage=HP,
            IRCNick=I,
            PGPKey=K,
            LangPreference=L,
            Timezone=TZ,
            CommentNotify=CN,
            UpdateNotify=UN,
            OwnershipNotify=ON,
            ResetKey=resetkey,
            AccountType=atype,
        )

    # If a PK was given and either one does not exist or the given
    # PK mismatches the existing user's SSHPubKey.PubKey.
    if PK:
        # Get the second element in the PK, which is the actual key.
        pubkey = PK.strip().rstrip()
        parts = pubkey.split(" ")
        if len(parts) == 3:
            # Remove the host part.
            pubkey = parts[0] + " " + parts[1]
        fingerprint = get_fingerprint(pubkey)
        with db.begin():
            user.ssh_pub_key = models.SSHPubKey(
                UserID=user.ID, PubKey=pubkey, Fingerprint=fingerprint
            )

    # Send a reset key notification to the new user.
    WelcomeNotification(user.ID).send()

    context["complete"] = True
    context["user"] = user
    return render_template(request, "register.html", context)

async def account_register_post(
    request: Request,
    U: str = Form(default=str()),  # Username
    E: str = Form(default=str()),  # Email
    H: str = Form(default="off"),  # Hide Email
    BE: str = Form(default=None),  # Backup Email
    R: str = Form(default=""),  # Real Name
    HP: str = Form(default=None),  # Homepage
    I: str = Form(default=None),  # IRC Nick # noqa: E741
    K: str = Form(default=None),  # PGP Key
    L: str = Form(default=aurweb.config.get("options", "default_lang")),
    TZ: str = Form(default=aurweb.config.get("options", "default_timezone")),
    PK: str = Form(default=None),  # SSH PubKey
    CN: bool = Form(default=False),
    UN: bool = Form(default=False),
    ON: bool = Form(default=False),
    captcha: str = Form(default=None),
    captcha_salt: str = Form(...),
):
    context = await make_variable_context(request, "Register")
    args = dict(await request.form())

    context = make_account_form_context(context, request, None, args)
    ok, errors = process_account_form(request, request.user, args)
    if not ok:
        # If the field values given do not meet the requirements,
        # return HTTP 400 with an error.
        context["errors"] = errors
        return render_template(
            request, "register.html", context, status_code=HTTPStatus.BAD_REQUEST
        )

    if not captcha:
        context["errors"] = ["The CAPTCHA is missing."]
        return render_template(
            request, "register.html", context, status_code=HTTPStatus.BAD_REQUEST
        )

    # Create a user with no password with a resetkey, then send
    # an email off about it.
    resetkey = generate_resetkey()

    # By default, we grab the User account type to associate with.
    atype = db.query(
        models.AccountType, models.AccountType.AccountType == "User"
    ).first()

    # Check if we should turn on HideEmail.
    if H == "on":
        hide_email = 1
    else:
        hide_email = 0

    # Create a user given all parameters available.
    with db.begin():
        user = db.create(
            models.User,
            Username=U,
            Email=E,
            HideEmail=hide_email,
            BackupEmail=BE,
            RealName=R,
            Homepage=HP,
            IRCNick=I,
            PGPKey=K,
            LangPreference=L,
            Timezone=TZ,
            CommentNotify=CN,
            UpdateNotify=UN,
            OwnershipNotify=ON,
            ResetKey=resetkey,
            AccountType=atype,
        )

    # If a PK was given and either one does not exist or the given
    # PK mismatches the existing user's SSHPubKey.PubKey.
    if PK:
        # Get the second element in the PK, which is the actual key.
        pubkey = PK.strip().rstrip()
        parts = pubkey.split(" ")
        if len(parts) == 3:
            # Remove the host part.
            pubkey = parts[0] + " " + parts[1]
        fingerprint = get_fingerprint(pubkey)
        with db.begin():
            user.ssh_pub_key = models.SSHPubKey(
                UserID=user.ID, PubKey=pubkey, Fingerprint=fingerprint
            )

    # Send a reset key notification to the new user.
    WelcomeNotification(user.ID).send()

    context["complete"] = True
    context["user"] = user
    return render_template(request, "register.html", context)

--- a/aurweb/routers/accounts.py
+++ b/aurweb/routers/accounts.py
@@ -2,7 +2,7 @@
     request: Request,
     U: str = Form(default=str()),  # Username
     E: str = Form(default=str()),  # Email
-    H: str = Form(default=False),  # Hide Email
+    H: str = Form(default="off"),  # Hide Email
     BE: str = Form(default=None),  # Backup Email
     R: str = Form(default=""),  # Real Name
     HP: str = Form(default=None),  # Homepage
@@ -45,13 +45,19 @@
         models.AccountType, models.AccountType.AccountType == "User"
     ).first()

+    # Check if we should turn on HideEmail.
+    if H == "on":
+        hide_email = 1
+    else:
+        hide_email = 0
+
     # Create a user given all parameters available.
     with db.begin():
         user = db.create(
             models.User,
             Username=U,
             Email=E,
-            HideEmail=H,
+            HideEmail=hide_email,
             BackupEmail=BE,
             RealName=R,
             Homepage=HP,