Mongoose before 8.8.3 can improperly use $where in match, leading to search injection.

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

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

module.exports = function getModelsMapForPopulate(model, docs, options) {
  let doc;
  const len = docs.length;
  const map = [];
  const modelNameFromQuery = options.model && options.model.modelName || options.model;
  let schema;
  let refPath;
  let modelNames;
  const available = {};

  const modelSchema = model.schema;

  // Populating a nested path should always be a no-op re: #9073.
  // People shouldn't do this, but apparently they do.
  if (options._localModel != null && options._localModel.schema.nested[options.path]) {
    return [];
  }

  const _virtualRes = getVirtual(model.schema, options.path);
  const virtual = _virtualRes == null ? null : _virtualRes.virtual;
  if (virtual != null) {
    return _virtualPopulate(model, docs, options, _virtualRes);
  }

  let allSchemaTypes = getSchemaTypes(model, modelSchema, null, options.path);
  allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);

  const isStrictPopulateDisabled = options.strictPopulate === false || options.options?.strictPopulate === false;
  if (!isStrictPopulateDisabled && allSchemaTypes.length === 0 && options._localModel != null) {
    return new StrictPopulate(options._fullPath || options.path);
  }

  for (let i = 0; i < len; i++) {
    doc = docs[i];
    let justOne = null;

    const docSchema = doc != null && doc.$__ != null ? doc.$__schema : modelSchema;
    schema = getSchemaTypes(model, docSchema, doc, options.path);

    // Special case: populating a path that's a DocumentArray unless
    // there's an explicit `ref` or `refPath` re: gh-8946
    if (schema != null &&
        schema.$isMongooseDocumentArray &&
        schema.options.ref == null &&
        schema.options.refPath == null) {
      continue;
    }
    const isUnderneathDocArray = schema && schema.$parentSchemaDocArray;
    if (isUnderneathDocArray && get(options, 'options.sort') != null) {
      return new MongooseError('Cannot populate with `sort` on path ' + options.path +
        ' because it is a subproperty of a document array');
    }

    modelNames = null;
    let isRefPath = false;
    let normalizedRefPath = null;
    let schemaOptions = null;
    let modelNamesInOrder = null;

    if (schema != null && schema.instance === 'Embedded') {
      if (schema.options.ref) {
        const data = {
          localField: options.path + '._id',
          foreignField: '_id',
          justOne: true
        };
        const res = _getModelNames(doc, schema, modelNameFromQuery, model);

        const unpopulatedValue = mpath.get(options.path, doc);
        const id = mpath.get('_id', unpopulatedValue);
        addModelNamesToMap(model, map, available, res.modelNames, options, data, id, doc, schemaOptions, unpopulatedValue);
      }
      // No-op if no `ref` set. See gh-11538
      continue;
    }

    if (Array.isArray(schema)) {
      const schemasArray = schema;
      for (const _schema of schemasArray) {
        let _modelNames;
        let res;
        try {
          res = _getModelNames(doc, _schema, modelNameFromQuery, model);
          _modelNames = res.modelNames;
          isRefPath = isRefPath || res.isRefPath;
          normalizedRefPath = normalizedRefPath || res.refPath;
          justOne = res.justOne;
        } catch (error) {
          return error;
        }

        if (isRefPath && !res.isRefPath) {
          continue;
        }
        if (!_modelNames) {
          continue;
        }
        modelNames = modelNames || [];
        for (const modelName of _modelNames) {
          if (modelNames.indexOf(modelName) === -1) {
            modelNames.push(modelName);
          }
        }
      }
    } else {
      try {
        const res = _getModelNames(doc, schema, modelNameFromQuery, model);
        modelNames = res.modelNames;
        isRefPath = res.isRefPath;
        normalizedRefPath = normalizedRefPath || res.refPath;
        justOne = res.justOne;
        schemaOptions = get(schema, 'options.populate', null);
        // Dedupe, because `refPath` can return duplicates of the same model name,
        // and that causes perf issues.
        if (isRefPath) {
          modelNamesInOrder = modelNames;
          modelNames = Array.from(new Set(modelNames));
        }
      } catch (error) {
        return error;
      }

      if (!modelNames) {
        continue;
      }
    }

    const data = {};
    const localField = options.path;
    const foreignField = '_id';

    // `justOne = null` means we don't know from the schema whether the end
    // result should be an array or a single doc. This can result from
    // populating a POJO using `Model.populate()`
    if ('justOne' in options && options.justOne !== void 0) {
      justOne = options.justOne;
    } else if (schema && !schema[schemaMixedSymbol]) {
      // Skip Mixed types because we explicitly don't do casting on those.
      if (options.path.endsWith('.' + schema.path) || options.path === schema.path) {
        justOne = Array.isArray(schema) ?
          schema.every(schema => !schema.$isMongooseArray) :
          !schema.$isMongooseArray;
      }
    }

    if (!modelNames) {
      continue;
    }

    data.isVirtual = false;
    data.justOne = justOne;
    data.localField = localField;
    data.foreignField = foreignField;

    // Get local fields
    const ret = _getLocalFieldValues(doc, localField, model, options, null, schema);

    const id = String(utils.getValue(foreignField, doc));
    options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;

    let match = get(options, 'match', null);

    const hasMatchFunction = typeof match === 'function';
    if (hasMatchFunction) {
      match = match.call(doc, doc);
    }
    data.match = match;
    data.hasMatchFunction = hasMatchFunction;
    data.isRefPath = isRefPath;
    data.modelNamesInOrder = modelNamesInOrder;

    if (isRefPath) {
      const embeddedDiscriminatorModelNames = _findRefPathForDiscriminators(doc,
        modelSchema, data, options, normalizedRefPath, ret);

      modelNames = embeddedDiscriminatorModelNames || modelNames;
    }

    try {
      addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc, schemaOptions);
    } catch (err) {
      return err;
    }
  }
  return map;

  function _getModelNames(doc, schema, modelNameFromQuery, model) {
    let modelNames;
    let isRefPath = false;
    let justOne = null;

    const originalSchema = schema;
    if (schema && schema.instance === 'Array') {
      schema = schema.caster;
    }
    if (schema && schema.$isSchemaMap) {
      schema = schema.$__schemaType;
    }

    const ref = schema && schema.options && schema.options.ref;
    refPath = schema && schema.options && schema.options.refPath;
    if (schema != null &&
        schema[schemaMixedSymbol] &&
        !ref &&
        !refPath &&
        !modelNameFromQuery) {
      return { modelNames: null };
    }

    if (modelNameFromQuery) {
      modelNames = [modelNameFromQuery]; // query options
    } else if (refPath != null) {
      if (typeof refPath === 'function') {
        const subdocPath = options.path.slice(0, options.path.length - schema.path.length - 1);
        const vals = mpath.get(subdocPath, doc, lookupLocalFields);
        const subdocsBeingPopulated = Array.isArray(vals) ?
          utils.array.flatten(vals) :
          (vals ? [vals] : []);

        modelNames = new Set();
        for (const subdoc of subdocsBeingPopulated) {
          refPath = refPath.call(subdoc, subdoc, options.path);
          modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
            forEach(name => modelNames.add(name));
        }
        modelNames = Array.from(modelNames);
      } else {
        modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
      }

      isRefPath = true;
    } else {
      let ref;
      let refPath;
      let schemaForCurrentDoc;
      let discriminatorValue;
      let modelForCurrentDoc = model;
      const discriminatorKey = model.schema.options.discriminatorKey;

      if (!schema && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) {
        // `modelNameForFind` is the discriminator value, so we might need
        // find the discriminated model name
        const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model;
        if (discriminatorModel != null) {
          modelForCurrentDoc = discriminatorModel;
        } else {
          try {
            modelForCurrentDoc = _getModelFromConn(model.db, discriminatorValue);
          } catch (error) {
            return error;
          }
        }

        schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);

        if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
          schemaForCurrentDoc = schemaForCurrentDoc.caster;
        }
      } else {
        schemaForCurrentDoc = schema;
      }

      if (originalSchema && originalSchema.path.endsWith('.$*')) {
        justOne = !originalSchema.$isMongooseArray && !originalSchema._arrayPath;
      } else if (schemaForCurrentDoc != null) {
        justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
      }

      if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
        if (schemaForCurrentDoc != null &&
            typeof ref === 'function' &&
            options.path.endsWith('.' + schemaForCurrentDoc.path)) {
          // Ensure correct context for ref functions: subdoc, not top-level doc. See gh-8469
          modelNames = new Set();

          const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
          const vals = mpath.get(subdocPath, doc, lookupLocalFields);
          const subdocsBeingPopulated = Array.isArray(vals) ?
            utils.array.flatten(vals) :
            (vals ? [vals] : []);
          for (const subdoc of subdocsBeingPopulated) {
            modelNames.add(handleRefFunction(ref, subdoc));
          }

          if (subdocsBeingPopulated.length === 0) {
            modelNames = [handleRefFunction(ref, doc)];
          } else {
            modelNames = Array.from(modelNames);
          }
        } else {
          ref = handleRefFunction(ref, doc);
          modelNames = [ref];
        }
      } else if ((schemaForCurrentDoc = get(schema, 'options.refPath')) != null) {
        isRefPath = true;
        if (typeof refPath === 'function') {
          const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
          const vals = mpath.get(subdocPath, doc, lookupLocalFields);
          const subdocsBeingPopulated = Array.isArray(vals) ?
            utils.array.flatten(vals) :
            (vals ? [vals] : []);

          modelNames = new Set();
          for (const subdoc of subdocsBeingPopulated) {
            refPath = refPath.call(subdoc, subdoc, options.path);
            modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
              forEach(name => modelNames.add(name));
          }
          modelNames = Array.from(modelNames);
        } else {
          modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
        }
      }
    }

    if (!modelNames) {
      // `Model.populate()` on a POJO with no known local model. Default to using the `Model`
      if (options._localModel == null) {
        modelNames = [model.modelName];
      } else {
        return { modelNames: modelNames, justOne: justOne, isRefPath: isRefPath, refPath: refPath };
      }
    }

    if (!Array.isArray(modelNames)) {
      modelNames = [modelNames];
    }

    return { modelNames: modelNames, justOne: justOne, isRefPath: isRefPath, refPath: refPath };
  }
};

function _virtualPopulate(model, docs, options, _virtualRes) {
  const map = [];
  const available = {};
  const virtual = _virtualRes.virtual;

  for (const doc of docs) {
    let modelNames = null;
    const data = {};

    // localField and foreignField
    let localField;
    const virtualPrefix = _virtualRes.nestedSchemaPath ?
      _virtualRes.nestedSchemaPath + '.' : '';
    if (typeof options.localField === 'string') {
      localField = options.localField;
    } else if (typeof virtual.options.localField === 'function') {
      localField = virtualPrefix + virtual.options.localField.call(doc, doc);
    } else if (Array.isArray(virtual.options.localField)) {
      localField = virtual.options.localField.map(field => virtualPrefix + field);
    } else {
      localField = virtualPrefix + virtual.options.localField;
    }
    data.count = virtual.options.count;

    if (virtual.options.skip != null && !options.hasOwnProperty('skip')) {
      options.skip = virtual.options.skip;
    }
    if (virtual.options.limit != null && !options.hasOwnProperty('limit')) {
      options.limit = virtual.options.limit;
    }
    if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) {
      options.perDocumentLimit = virtual.options.perDocumentLimit;
    }
    let foreignField = virtual.options.foreignField;

    if (!localField || !foreignField) {
      return new MongooseError(`Cannot populate virtual \`${options.path}\` on model \`${model.modelName}\`, because options \`localField\` and / or \`foreignField\` are missing`);
    }

    if (typeof localField === 'function') {
      localField = localField.call(doc, doc);
    }
    if (typeof foreignField === 'function') {
      foreignField = foreignField.call(doc, doc);
    }

    data.isRefPath = false;

    // `justOne = null` means we don't know from the schema whether the end
    // result should be an array or a single doc. This can result from
    // populating a POJO using `Model.populate()`
    let justOne = null;
    if ('justOne' in options && options.justOne !== void 0) {
      justOne = options.justOne;
    }

    modelNames = virtual._getModelNamesForPopulate(doc);
    if (virtual.options.refPath) {
      justOne = !!virtual.options.justOne;
      data.isRefPath = true;
    } else if (virtual.options.ref) {
      justOne = !!virtual.options.justOne;
    }

    data.isVirtual = true;
    data.virtual = virtual;
    data.justOne = justOne;

    // `match`
    const baseMatch = get(data, 'virtual.options.match', null) ||
      get(data, 'virtual.options.options.match', null);
    let match = get(options, 'match', null) || baseMatch;

    let hasMatchFunction = typeof match === 'function';
    if (hasMatchFunction) {
      match = match.call(doc, doc, data.virtual);
    }

    if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
      match = Object.assign({}, match);
      for (let i = 1; i < localField.length; ++i) {
        match[foreignField[i]] = convertTo_id(mpath.get(localField[i], doc, lookupLocalFields), model.schema);
        hasMatchFunction = true;
      }

      localField = localField[0];
      foreignField = foreignField[0];
    }
    data.localField = localField;
    data.foreignField = foreignField;
    data.match = match;
    data.hasMatchFunction = hasMatchFunction;

    // Get local fields
    const ret = _getLocalFieldValues(doc, localField, model, options, virtual);

    try {
      addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc);
    } catch (err) {
      return err;
    }
  }

  return map;
}

module.exports = function getModelsMapForPopulate(model, docs, options) {
  let doc;
  const len = docs.length;
  const map = [];
  const modelNameFromQuery = options.model && options.model.modelName || options.model;
  let schema;
  let refPath;
  let modelNames;
  const available = {};

  const modelSchema = model.schema;

  // Populating a nested path should always be a no-op re: #9073.
  // People shouldn't do this, but apparently they do.
  if (options._localModel != null && options._localModel.schema.nested[options.path]) {
    return [];
  }

  const _virtualRes = getVirtual(model.schema, options.path);
  const virtual = _virtualRes == null ? null : _virtualRes.virtual;
  if (virtual != null) {
    return _virtualPopulate(model, docs, options, _virtualRes);
  }

  let allSchemaTypes = getSchemaTypes(model, modelSchema, null, options.path);
  allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);

  const isStrictPopulateDisabled = options.strictPopulate === false || options.options?.strictPopulate === false;
  if (!isStrictPopulateDisabled && allSchemaTypes.length === 0 && options._localModel != null) {
    return new StrictPopulate(options._fullPath || options.path);
  }

  for (let i = 0; i < len; i++) {
    doc = docs[i];
    let justOne = null;

    const docSchema = doc != null && doc.$__ != null ? doc.$__schema : modelSchema;
    schema = getSchemaTypes(model, docSchema, doc, options.path);

    // Special case: populating a path that's a DocumentArray unless
    // there's an explicit `ref` or `refPath` re: gh-8946
    if (schema != null &&
        schema.$isMongooseDocumentArray &&
        schema.options.ref == null &&
        schema.options.refPath == null) {
      continue;
    }
    const isUnderneathDocArray = schema && schema.$parentSchemaDocArray;
    if (isUnderneathDocArray && get(options, 'options.sort') != null) {
      return new MongooseError('Cannot populate with `sort` on path ' + options.path +
        ' because it is a subproperty of a document array');
    }

    modelNames = null;
    let isRefPath = false;
    let normalizedRefPath = null;
    let schemaOptions = null;
    let modelNamesInOrder = null;

    if (schema != null && schema.instance === 'Embedded') {
      if (schema.options.ref) {
        const data = {
          localField: options.path + '._id',
          foreignField: '_id',
          justOne: true
        };
        const res = _getModelNames(doc, schema, modelNameFromQuery, model);

        const unpopulatedValue = mpath.get(options.path, doc);
        const id = mpath.get('_id', unpopulatedValue);
        addModelNamesToMap(model, map, available, res.modelNames, options, data, id, doc, schemaOptions, unpopulatedValue);
      }
      // No-op if no `ref` set. See gh-11538
      continue;
    }

    if (Array.isArray(schema)) {
      const schemasArray = schema;
      for (const _schema of schemasArray) {
        let _modelNames;
        let res;
        try {
          res = _getModelNames(doc, _schema, modelNameFromQuery, model);
          _modelNames = res.modelNames;
          isRefPath = isRefPath || res.isRefPath;
          normalizedRefPath = normalizedRefPath || res.refPath;
          justOne = res.justOne;
        } catch (error) {
          return error;
        }

        if (isRefPath && !res.isRefPath) {
          continue;
        }
        if (!_modelNames) {
          continue;
        }
        modelNames = modelNames || [];
        for (const modelName of _modelNames) {
          if (modelNames.indexOf(modelName) === -1) {
            modelNames.push(modelName);
          }
        }
      }
    } else {
      try {
        const res = _getModelNames(doc, schema, modelNameFromQuery, model);
        modelNames = res.modelNames;
        isRefPath = res.isRefPath;
        normalizedRefPath = normalizedRefPath || res.refPath;
        justOne = res.justOne;
        schemaOptions = get(schema, 'options.populate', null);
        // Dedupe, because `refPath` can return duplicates of the same model name,
        // and that causes perf issues.
        if (isRefPath) {
          modelNamesInOrder = modelNames;
          modelNames = Array.from(new Set(modelNames));
        }
      } catch (error) {
        return error;
      }

      if (!modelNames) {
        continue;
      }
    }

    const data = {};
    const localField = options.path;
    const foreignField = '_id';

    // `justOne = null` means we don't know from the schema whether the end
    // result should be an array or a single doc. This can result from
    // populating a POJO using `Model.populate()`
    if ('justOne' in options && options.justOne !== void 0) {
      justOne = options.justOne;
    } else if (schema && !schema[schemaMixedSymbol]) {
      // Skip Mixed types because we explicitly don't do casting on those.
      if (options.path.endsWith('.' + schema.path) || options.path === schema.path) {
        justOne = Array.isArray(schema) ?
          schema.every(schema => !schema.$isMongooseArray) :
          !schema.$isMongooseArray;
      }
    }

    if (!modelNames) {
      continue;
    }

    data.isVirtual = false;
    data.justOne = justOne;
    data.localField = localField;
    data.foreignField = foreignField;

    // Get local fields
    const ret = _getLocalFieldValues(doc, localField, model, options, null, schema);

    const id = String(utils.getValue(foreignField, doc));
    options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;

    let match = get(options, 'match', null);

    const hasMatchFunction = typeof match === 'function';
    if (hasMatchFunction) {
      match = match.call(doc, doc);
    }
    if (Array.isArray(match)) {
      for (const item of match) {
        if (item != null && item.$where) {
          throw new MongooseError('Cannot use $where filter with populate() match');
        }
      }
    } else if (match != null && match.$where != null) {
      throw new MongooseError('Cannot use $where filter with populate() match');
    }
    data.match = match;
    data.hasMatchFunction = hasMatchFunction;
    data.isRefPath = isRefPath;
    data.modelNamesInOrder = modelNamesInOrder;

    if (isRefPath) {
      const embeddedDiscriminatorModelNames = _findRefPathForDiscriminators(doc,
        modelSchema, data, options, normalizedRefPath, ret);

      modelNames = embeddedDiscriminatorModelNames || modelNames;
    }

    try {
      addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc, schemaOptions);
    } catch (err) {
      return err;
    }
  }
  return map;

  function _getModelNames(doc, schema, modelNameFromQuery, model) {
    let modelNames;
    let isRefPath = false;
    let justOne = null;

    const originalSchema = schema;
    if (schema && schema.instance === 'Array') {
      schema = schema.caster;
    }
    if (schema && schema.$isSchemaMap) {
      schema = schema.$__schemaType;
    }

    const ref = schema && schema.options && schema.options.ref;
    refPath = schema && schema.options && schema.options.refPath;
    if (schema != null &&
        schema[schemaMixedSymbol] &&
        !ref &&
        !refPath &&
        !modelNameFromQuery) {
      return { modelNames: null };
    }

    if (modelNameFromQuery) {
      modelNames = [modelNameFromQuery]; // query options
    } else if (refPath != null) {
      if (typeof refPath === 'function') {
        const subdocPath = options.path.slice(0, options.path.length - schema.path.length - 1);
        const vals = mpath.get(subdocPath, doc, lookupLocalFields);
        const subdocsBeingPopulated = Array.isArray(vals) ?
          utils.array.flatten(vals) :
          (vals ? [vals] : []);

        modelNames = new Set();
        for (const subdoc of subdocsBeingPopulated) {
          refPath = refPath.call(subdoc, subdoc, options.path);
          modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
            forEach(name => modelNames.add(name));
        }
        modelNames = Array.from(modelNames);
      } else {
        modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
      }

      isRefPath = true;
    } else {
      let ref;
      let refPath;
      let schemaForCurrentDoc;
      let discriminatorValue;
      let modelForCurrentDoc = model;
      const discriminatorKey = model.schema.options.discriminatorKey;

      if (!schema && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) {
        // `modelNameForFind` is the discriminator value, so we might need
        // find the discriminated model name
        const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model;
        if (discriminatorModel != null) {
          modelForCurrentDoc = discriminatorModel;
        } else {
          try {
            modelForCurrentDoc = _getModelFromConn(model.db, discriminatorValue);
          } catch (error) {
            return error;
          }
        }

        schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);

        if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
          schemaForCurrentDoc = schemaForCurrentDoc.caster;
        }
      } else {
        schemaForCurrentDoc = schema;
      }

      if (originalSchema && originalSchema.path.endsWith('.$*')) {
        justOne = !originalSchema.$isMongooseArray && !originalSchema._arrayPath;
      } else if (schemaForCurrentDoc != null) {
        justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
      }

      if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
        if (schemaForCurrentDoc != null &&
            typeof ref === 'function' &&
            options.path.endsWith('.' + schemaForCurrentDoc.path)) {
          // Ensure correct context for ref functions: subdoc, not top-level doc. See gh-8469
          modelNames = new Set();

          const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
          const vals = mpath.get(subdocPath, doc, lookupLocalFields);
          const subdocsBeingPopulated = Array.isArray(vals) ?
            utils.array.flatten(vals) :
            (vals ? [vals] : []);
          for (const subdoc of subdocsBeingPopulated) {
            modelNames.add(handleRefFunction(ref, subdoc));
          }

          if (subdocsBeingPopulated.length === 0) {
            modelNames = [handleRefFunction(ref, doc)];
          } else {
            modelNames = Array.from(modelNames);
          }
        } else {
          ref = handleRefFunction(ref, doc);
          modelNames = [ref];
        }
      } else if ((schemaForCurrentDoc = get(schema, 'options.refPath')) != null) {
        isRefPath = true;
        if (typeof refPath === 'function') {
          const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
          const vals = mpath.get(subdocPath, doc, lookupLocalFields);
          const subdocsBeingPopulated = Array.isArray(vals) ?
            utils.array.flatten(vals) :
            (vals ? [vals] : []);

          modelNames = new Set();
          for (const subdoc of subdocsBeingPopulated) {
            refPath = refPath.call(subdoc, subdoc, options.path);
            modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
              forEach(name => modelNames.add(name));
          }
          modelNames = Array.from(modelNames);
        } else {
          modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
        }
      }
    }

    if (!modelNames) {
      // `Model.populate()` on a POJO with no known local model. Default to using the `Model`
      if (options._localModel == null) {
        modelNames = [model.modelName];
      } else {
        return { modelNames: modelNames, justOne: justOne, isRefPath: isRefPath, refPath: refPath };
      }
    }

    if (!Array.isArray(modelNames)) {
      modelNames = [modelNames];
    }

    return { modelNames: modelNames, justOne: justOne, isRefPath: isRefPath, refPath: refPath };
  }
};

--- a/lib/helpers/populate/getModelsMapForPopulate.js
+++ b/lib/helpers/populate/getModelsMapForPopulate.js
@@ -164,6 +164,15 @@
     if (hasMatchFunction) {
       match = match.call(doc, doc);
     }
+    if (Array.isArray(match)) {
+      for (const item of match) {
+        if (item != null && item.$where) {
+          throw new MongooseError('Cannot use $where filter with populate() match');
+        }
+      }
+    } else if (match != null && match.$where != null) {
+      throw new MongooseError('Cannot use $where filter with populate() match');
+    }
     data.match = match;
     data.hasMatchFunction = hasMatchFunction;
     data.isRefPath = isRefPath;

--- a/lib/helpers/populate/getModelsMapForPopulate.js
+++ b/lib/helpers/populate/getModelsMapForPopulate.js
@@ -1,105 +1,59 @@
-function _virtualPopulate(model, docs, options, _virtualRes) {
-  const map = [];
-  const available = {};
-  const virtual = _virtualRes.virtual;
-
-  for (const doc of docs) {
-    let modelNames = null;
-    const data = {};
-
-    // localField and foreignField
-    let localField;
-    const virtualPrefix = _virtualRes.nestedSchemaPath ?
-      _virtualRes.nestedSchemaPath + '.' : '';
-    if (typeof options.localField === 'string') {
-      localField = options.localField;
-    } else if (typeof virtual.options.localField === 'function') {
-      localField = virtualPrefix + virtual.options.localField.call(doc, doc);
-    } else if (Array.isArray(virtual.options.localField)) {
-      localField = virtual.options.localField.map(field => virtualPrefix + field);
-    } else {
-      localField = virtualPrefix + virtual.options.localField;
-    }
-    data.count = virtual.options.count;
-
-    if (virtual.options.skip != null && !options.hasOwnProperty('skip')) {
-      options.skip = virtual.options.skip;
-    }
-    if (virtual.options.limit != null && !options.hasOwnProperty('limit')) {
-      options.limit = virtual.options.limit;
-    }
-    if (virtual.options.perDocumentLimit != null && !options.hasOwnProperty('perDocumentLimit')) {
-      options.perDocumentLimit = virtual.options.perDocumentLimit;
-    }
-    let foreignField = virtual.options.foreignField;
-
-    if (!localField || !foreignField) {
-      return new MongooseError(`Cannot populate virtual \`${options.path}\` on model \`${model.modelName}\`, because options \`localField\` and / or \`foreignField\` are missing`);
-    }
-
-    if (typeof localField === 'function') {
-      localField = localField.call(doc, doc);
-    }
-    if (typeof foreignField === 'function') {
-      foreignField = foreignField.call(doc, doc);
-    }
-
-    data.isRefPath = false;
-
-    // `justOne = null` means we don't know from the schema whether the end
-    // result should be an array or a single doc. This can result from
-    // populating a POJO using `Model.populate()`
-    let justOne = null;
-    if ('justOne' in options && options.justOne !== void 0) {
-      justOne = options.justOne;
-    }
-
-    modelNames = virtual._getModelNamesForPopulate(doc);
-    if (virtual.options.refPath) {
-      justOne = !!virtual.options.justOne;
-      data.isRefPath = true;
-    } else if (virtual.options.ref) {
-      justOne = !!virtual.options.justOne;
-    }
-
-    data.isVirtual = true;
-    data.virtual = virtual;
-    data.justOne = justOne;
-
-    // `match`
-    const baseMatch = get(data, 'virtual.options.match', null) ||
-      get(data, 'virtual.options.options.match', null);
-    let match = get(options, 'match', null) || baseMatch;
-
-    let hasMatchFunction = typeof match === 'function';
-    if (hasMatchFunction) {
-      match = match.call(doc, doc, data.virtual);
-    }
-
-    if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
-      match = Object.assign({}, match);
-      for (let i = 1; i < localField.length; ++i) {
-        match[foreignField[i]] = convertTo_id(mpath.get(localField[i], doc, lookupLocalFields), model.schema);
-        hasMatchFunction = true;
+        for (const subdoc of subdocsBeingPopulated) {
+          refPath = refPath.call(subdoc, subdoc, options.path);
+          modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
+            forEach(name => modelNames.add(name));
+        }
+        modelNames = Array.from(modelNames);
+      } else {
+        modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
       }

-      localField = localField[0];
-      foreignField = foreignField[0];
-    }
-    data.localField = localField;
-    data.foreignField = foreignField;
-    data.match = match;
-    data.hasMatchFunction = hasMatchFunction;
+      isRefPath = true;
+    } else {
+      let ref;
+      let refPath;
+      let schemaForCurrentDoc;
+      let discriminatorValue;
+      let modelForCurrentDoc = model;
+      const discriminatorKey = model.schema.options.discriminatorKey;

-    // Get local fields
-    const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
+      if (!schema && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) {
+        // `modelNameForFind` is the discriminator value, so we might need
+        // find the discriminated model name
+        const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model;
+        if (discriminatorModel != null) {
+          modelForCurrentDoc = discriminatorModel;
+        } else {
+          try {
+            modelForCurrentDoc = _getModelFromConn(model.db, discriminatorValue);
+          } catch (error) {
+            return error;
+          }
+        }

-    try {
-      addModelNamesToMap(model, map, available, modelNames, options, data, ret, doc);
-    } catch (err) {
-      return err;
-    }
-  }
+        schemaForCurrentDoc = modelForCurrentDoc.schema._getSchema(options.path);

-  return map;
-}
+        if (schemaForCurrentDoc && schemaForCurrentDoc.caster) {
+          schemaForCurrentDoc = schemaForCurrentDoc.caster;
+        }
+      } else {
+        schemaForCurrentDoc = schema;
+      }
+
+      if (originalSchema && originalSchema.path.endsWith('.$*')) {
+        justOne = !originalSchema.$isMongooseArray && !originalSchema._arrayPath;
+      } else if (schemaForCurrentDoc != null) {
+        justOne = !schemaForCurrentDoc.$isMongooseArray && !schemaForCurrentDoc._arrayPath;
+      }
+
+      if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
+        if (schemaForCurrentDoc != null &&
+            typeof ref === 'function' &&
+            options.path.endsWith('.' + schemaForCurrentDoc.path)) {
+          // Ensure correct context for ref functions: subdoc, not top-level doc. See gh-8469
+          modelNames = new Set();
+
+          const subdocPath = options.path.slice(0, options.path.length - schemaForCurrentDoc.path.length - 1);
+          const vals = mpath.get(subdocPath, doc, lookupLocalFields);
+          const subdocsBeingPopulated = Array.isArray(vals) ?
+            utils.array.flatten(vals) :

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

cd /workspace/mongoose
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx mocha --grep "match prevents using $where"

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

cd /workspace/mongoose
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha --timeout 6000 test/model.populate.test.js


MSS (Mission Support System) is an open source package designed for planning atmospheric research flights. In file: `index.py`, there is a method that is vulnerable to path manipulation attack. By modifying file paths, an attacker can acquire sensitive information from different resources. The `filename` variable is joined with other variables to form a file path in `_file`. However, `filename` is a route parameter that can capture path type values i.e. values including slashes (\). So it is possible for an attacker to manipulate the file being read by assigning a value containing ../ to `filename` and so the attacker may be able to gain access to other files on the host filesystem. This issue has been addressed in MSS version 8.3.3. Users are advised to upgrade. There are no known workarounds for this vulnerability.

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

    def code(filename):
        download = request.args.get("download", False)
        _file = os.path.join(STATIC_LOCATION, 'code', filename)
        content = get_content(_file)
        if not download:
            return render_template("/content.html", act="code", content=content)
        else:
            with open(_file) as f:
                text = f.read()
            return Response("".join([s.replace("\t", "", 1) for s in text.split("```python")[-1]
                                    .splitlines(keepends=True)][1:-2]),
                            mimetype="text/plain",
                            headers={"Content-disposition": f"attachment; filename={filename.split('-')[0]}.py"})

def uploads(name=None, filename=None):
    base_path = mscolab_settings.UPLOAD_FOLDER
    if name is None:
        abort(404)
    if filename is None:
        abort(404)
    return send_from_directory(fs.path.join(base_path, name), filename)

    def code(filename):
        download = request.args.get("download", False)
        _file = werkzeug.security.safe_join(STATIC_LOCATION, "code", filename)
        if _file is None:
            abort(404)
        content = get_content(_file)
        if not download:
            return render_template("/content.html", act="code", content=content)
        else:
            if not os.path.isfile(_file):
                abort(404)
            with open(_file) as f:
                text = f.read()
            return Response("".join([s.replace("\t", "", 1) for s in text.split("```python")[-1]
                                    .splitlines(keepends=True)][1:-2]),
                            mimetype="text/plain",
                            headers={"Content-disposition": f"attachment; filename={filename.split('-')[0]}.py"})

--- a/mslib/index.py
+++ b/mslib/index.py
@@ -1,10 +1,14 @@
     def code(filename):
         download = request.args.get("download", False)
-        _file = os.path.join(STATIC_LOCATION, 'code', filename)
+        _file = werkzeug.security.safe_join(STATIC_LOCATION, "code", filename)
+        if _file is None:
+            abort(404)
         content = get_content(_file)
         if not download:
             return render_template("/content.html", act="code", content=content)
         else:
+            if not os.path.isfile(_file):
+                abort(404)
             with open(_file) as f:
                 text = f.read()
             return Response("".join([s.replace("\t", "", 1) for s in text.split("```python")[-1]

--- a/mslib/mscolab/server.py
+++ b/mslib/mscolab/server.py
@@ -1,7 +1,6 @@
-def uploads(name=None, filename=None):
-    base_path = mscolab_settings.UPLOAD_FOLDER
     if name is None:
         abort(404)
     if filename is None:
         abort(404)
     return send_from_directory(fs.path.join(base_path, name), filename)
+


A vulnerability classified as problematic has been found in OpenDNS OpenResolve. This affects an unknown part of the file resolverapi/endpoints.py. The manipulation leads to improper output neutralization for logs. The identifier of the patch is 9eba6ba5abd89d0e36a008921eb307fcef8c5311. It is recommended to apply a patch to fix this issue. The identifier VDB-217197 was assigned to this vulnerability.

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 get(self, rdtype, domain):
        t1 = time.time()

        rdtype = rdtype.upper()
        current_app.logger.info(
            'Request from %s - %s %s', request.remote_addr, rdtype, domain)
        self.valid_args(rdtype, domain)

        # Iterate through nameservers so that we can tell which one gets used.
        nameservers = current_app.config['RESOLVERS']
        for nameserver in nameservers:
            dns_resolver.nameservers = [nameserver]
            try:
                answer = dns_resolver.query(
                    domain, rdtype, raise_on_no_answer=False)
                # Successful query
                break
            except (NoNameservers, NXDOMAIN):
                # TODO: this should still follow the RFC
                return {'message': "No nameservers for %s" % domain}, 404
            except Timeout as e:
                # Communication fail or timeout - try next nameserver
                if nameserver is nameservers[-1]:
                    current_app.logger.info(e)
                    return {'message': 'All nameservers timed out.'}, 503
                continue
            except Exception as e:
                current_app.logger.error(e)
                return {'message': 'An unexpected error occured.'}, 500

        t2 = time.time()
        duration = t2 - t1

        return parse_query(answer, nameserver, duration)

    def get(self, rdtype, domain):
        t1 = time.time()

        rdtype = rdtype.upper()
        current_app.logger.info(
            'Request from %s - %s', request.remote_addr, rdtype)
        self.valid_args(rdtype, domain)

        # Iterate through nameservers so that we can tell which one gets used.
        nameservers = current_app.config['RESOLVERS']
        for nameserver in nameservers:
            dns_resolver.nameservers = [nameserver]
            try:
                answer = dns_resolver.query(
                    domain, rdtype, raise_on_no_answer=False)
                # Successful query
                break
            except (NoNameservers, NXDOMAIN):
                # TODO: this should still follow the RFC
                return {'message': "No nameservers for %s" % domain}, 404
            except Timeout as e:
                # Communication fail or timeout - try next nameserver
                if nameserver is nameservers[-1]:
                    current_app.logger.info(e)
                    return {'message': 'All nameservers timed out.'}, 503
                continue
            except Exception as e:
                current_app.logger.error(e)
                return {'message': 'An unexpected error occured.'}, 500

        t2 = time.time()
        duration = t2 - t1

        return parse_query(answer, nameserver, duration)

--- a/resolverapi/endpoints.py
+++ b/resolverapi/endpoints.py
@@ -3,7 +3,7 @@

         rdtype = rdtype.upper()
         current_app.logger.info(
-            'Request from %s - %s %s', request.remote_addr, rdtype, domain)
+            'Request from %s - %s', request.remote_addr, rdtype)
         self.valid_args(rdtype, domain)

         # Iterate through nameservers so that we can tell which one gets used.


Improper Access Control in GitHub repository francoisjacquet/rosariosis prior to 10.9.3.

Improper Access Control

The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.

window.onpageshow=function(event) {
	/**
	 * Same as above for Safari (does not execute Javascript on history back)
	 * persisted indicates if the document is loading from a cache (not reliable)
	 *
	 * @link https://web.dev/bfcache/
	 */
	if (event.persisted
		|| window.performance && (performance.navigation.type == 2
			|| (performance.getEntriesByType
				&& performance.getEntriesByType("navigation")[0]
				&& performance.getEntriesByType("navigation")[0].type === 'back_forward'))) {
		location.reload();
	}
};

window.onpageshow=function(event) {
	browserHistoryCacheBuster(event);
};

--- a/assets/js/warehouse.js
+++ b/assets/js/warehouse.js
@@ -1,15 +1,3 @@
 window.onpageshow=function(event) {
-	/**
-	 * Same as above for Safari (does not execute Javascript on history back)
-	 * persisted indicates if the document is loading from a cache (not reliable)
-	 *
-	 * @link https://web.dev/bfcache/
-	 */
-	if (event.persisted
-		|| window.performance && (performance.navigation.type == 2
-			|| (performance.getEntriesByType
-				&& performance.getEntriesByType("navigation")[0]
-				&& performance.getEntriesByType("navigation")[0].type === 'back_forward'))) {
-		location.reload();
-	}
+	browserHistoryCacheBuster(event);
 };


canonical/get-workflow-version-action is a GitHub composite action to get commit SHA that GitHub Actions reusable workflow was called with. Prior to 1.0.1, if the get-workflow-version-action step fails, the exception output may include the GITHUB_TOKEN. If the full token is included in the exception output, GitHub will automatically redact the secret from the GitHub Actions logs. However, the token may be truncated—causing part of the GITHUB_TOKEN to be displayed in plaintext in the GitHub Actions logs. Anyone with read access to the GitHub repository can view GitHub Actions logs. For public repositories, anyone can view the GitHub Actions logs. The opportunity to exploit this vulnerability is limited—the GITHUB_TOKEN is automatically revoked when the job completes. However, there is an opportunity for an attack in the time between the GITHUB_TOKEN being displayed in the logs and the completion of the job. Users using the github-token input are impacted. This vulnerability is fixed in 1.0.1.

Insertion of Sensitive Information into Log File

The product writes sensitive information to a log file.

--- a/get_workflow_version/main.py
+++ b/get_workflow_version/main.py
@@ -1 +1 @@
-app = typer.Typer()
+app = typer.Typer(pretty_exceptions_show_locals=False)


beego is an open-source web framework for the Go programming language. Versions of beego prior to 2.3.4 use MD5 as a hashing algorithm. MD5 is no longer considered secure against well-funded opponents due to its vulnerability to collision attacks. Version 2.3.4 replaces MD5 with SHA256.

Use of a Broken or Risky Cryptographic Algorithm

The product uses a broken or risky cryptographic algorithm or protocol.

func (fc *FileCache) getCacheFileName(key string) (string, error) {
	m := md5.New()
	_, _ = io.WriteString(m, key)
	keyMd5 := hex.EncodeToString(m.Sum(nil))
	cachePath := fc.CachePath
	switch fc.DirectoryLevel {
	case 2:
		cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
	case 1:
		cachePath = filepath.Join(cachePath, keyMd5[0:2])
	}
	ok, err := exists(cachePath)
	if err != nil {
		return "", err
	}
	if !ok {
		err = os.MkdirAll(cachePath, os.ModePerm)
		if err != nil {
			return "", berror.Wrapf(err, CreateFileCacheDirFailed,
				"could not create the directory: %s", cachePath)
		}
	}

	return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)), nil
}

func (fc *FileCache) getCacheFileName(key string) (string, error) {
	m := sha256.New()
	_, _ = io.WriteString(m, key)
	keySha256 := hex.EncodeToString(m.Sum(nil))
	cachePath := fc.CachePath
	switch fc.DirectoryLevel {
	case 2:
		cachePath = filepath.Join(cachePath, keySha256[0:2], keySha256[2:4])
	case 1:
		cachePath = filepath.Join(cachePath, keySha256[0:2])
	}
	ok, err := exists(cachePath)
	if err != nil {
		return "", err
	}
	if !ok {
		fmt.Printf("cachePath: %s\n", cachePath)
		err = os.MkdirAll(cachePath, os.ModePerm)
		if err != nil {
			return "", berror.Wrapf(err, CreateFileCacheDirFailed,
				"could not create the directory: %s", cachePath)
		}
	}

	return filepath.Join(cachePath, fmt.Sprintf("%s%s", keySha256, fc.FileSuffix)), nil
}

--- a/client/cache/file.go
+++ b/client/cache/file.go
@@ -1,19 +1,20 @@
 func (fc *FileCache) getCacheFileName(key string) (string, error) {
-	m := md5.New()
+	m := sha256.New()
 	_, _ = io.WriteString(m, key)
-	keyMd5 := hex.EncodeToString(m.Sum(nil))
+	keySha256 := hex.EncodeToString(m.Sum(nil))
 	cachePath := fc.CachePath
 	switch fc.DirectoryLevel {
 	case 2:
-		cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
+		cachePath = filepath.Join(cachePath, keySha256[0:2], keySha256[2:4])
 	case 1:
-		cachePath = filepath.Join(cachePath, keyMd5[0:2])
+		cachePath = filepath.Join(cachePath, keySha256[0:2])
 	}
 	ok, err := exists(cachePath)
 	if err != nil {
 		return "", err
 	}
 	if !ok {
+		fmt.Printf("cachePath: %s\n", cachePath)
 		err = os.MkdirAll(cachePath, os.ModePerm)
 		if err != nil {
 			return "", berror.Wrapf(err, CreateFileCacheDirFailed,
@@ -21,5 +22,5 @@
 		}
 	}

-	return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)), nil
+	return filepath.Join(cachePath, fmt.Sprintf("%s%s", keySha256, fc.FileSuffix)), nil
 }


Authorization Bypass Through User-Controlled Key in NPM url-parse prior to 1.5.8.

Missing Authorization

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

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.

var rules = [
  ['#', 'hash'],                        // Extract from the back.
  ['?', 'query'],                       // Extract from the back.
  function sanitize(address, url) {     // Sanitize what is left of the address
    return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address;
  },
  ['/', 'pathname'],                    // Extract from the back.
  ['@', 'auth', 1],                     // Extract from the front.
  [NaN, 'host', undefined, 1, 1],       // Set left over value.
  [/:(\d+)$/, 'port', undefined, 1],    // RegExp the back.
  [NaN, 'hostname', undefined, 1, 1]    // Set left over.
];

function toString(stringify) {
  if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;

  var query
    , url = this
    , protocol = url.protocol;

  if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';

  var result =
    protocol +
    ((url.protocol && url.slashes) || isSpecial(url.protocol) ? '//' : '');

  if (url.username) {
    result += url.username;
    if (url.password) result += ':'+ url.password;
    result += '@';
  } else if (url.password) {
    result += ':'+ url.password;
    result += '@';
  } else if (
    url.protocol !== 'file:' &&
    isSpecial(url.protocol) &&
    !url.host &&
    url.pathname !== '/'
  ) {
    //
    // Add back the empty userinfo, otherwise the original invalid URL
    // might be transformed into a valid one with `url.pathname` as host.
    //
    result += '@';
  }

  result += url.host + url.pathname;

  query = 'object' === typeof url.query ? stringify(url.query) : url.query;
  if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;

  if (url.hash) result += url.hash;

  return result;
}

var rules = [
  ['#', 'hash'],                        // Extract from the back.
  ['?', 'query'],                       // Extract from the back.
  function sanitize(address, url) {     // Sanitize what is left of the address
    return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address;
  },
  ['/', 'pathname'],                    // Extract from the back.
  ['@', 'auth', 1],                     // Extract from the front.
  [NaN, 'host', undefined, 1, 1],       // Set left over value.
  [/:(\d*)$/, 'port', undefined, 1],    // RegExp the back.
  [NaN, 'hostname', undefined, 1, 1]    // Set left over.
];

function toString(stringify) {
  if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify;

  var query
    , url = this
    , host = url.host
    , protocol = url.protocol;

  if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';

  var result =
    protocol +
    ((url.protocol && url.slashes) || isSpecial(url.protocol) ? '//' : '');

  if (url.username) {
    result += url.username;
    if (url.password) result += ':'+ url.password;
    result += '@';
  } else if (url.password) {
    result += ':'+ url.password;
    result += '@';
  } else if (
    url.protocol !== 'file:' &&
    isSpecial(url.protocol) &&
    !host &&
    url.pathname !== '/'
  ) {
    //
    // Add back the empty userinfo, otherwise the original invalid URL
    // might be transformed into a valid one with `url.pathname` as host.
    //
    result += '@';
  }

  //
  // Trailing colon is removed from `url.host` when it is parsed. If it still
  // ends with a colon, then add back the trailing colon that was removed. This
  // prevents an invalid URL from being transformed into a valid one.
  //
  if (host[host.length - 1] === ':') host += ':';
  result += host + url.pathname;

  query = 'object' === typeof url.query ? stringify(url.query) : url.query;
  if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;

  if (url.hash) result += url.hash;

  return result;
}

--- a/index.js
+++ b/index.js
@@ -7,6 +7,6 @@
   ['/', 'pathname'],                    // Extract from the back.
   ['@', 'auth', 1],                     // Extract from the front.
   [NaN, 'host', undefined, 1, 1],       // Set left over value.
-  [/:(\d+)$/, 'port', undefined, 1],    // RegExp the back.
+  [/:(\d*)$/, 'port', undefined, 1],    // RegExp the back.
   [NaN, 'hostname', undefined, 1, 1]    // Set left over.
 ];

--- a/index.js
+++ b/index.js
@@ -3,6 +3,7 @@

   var query
     , url = this
+    , host = url.host
     , protocol = url.protocol;

   if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':';
@@ -21,7 +22,7 @@
   } else if (
     url.protocol !== 'file:' &&
     isSpecial(url.protocol) &&
-    !url.host &&
+    !host &&
     url.pathname !== '/'
   ) {
     //
@@ -31,7 +32,13 @@
     result += '@';
   }

-  result += url.host + url.pathname;
+  //
+  // Trailing colon is removed from `url.host` when it is parsed. If it still
+  // ends with a colon, then add back the trailing colon that was removed. This
+  // prevents an invalid URL from being transformed into a valid one.
+  //
+  if (host[host.length - 1] === ':') host += ':';
+  result += host + url.pathname;

   query = 'object' === typeof url.query ? stringify(url.query) : url.query;
   if (query) result += '?' !== query.charAt(0) ? '?'+ query : query;

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

cd /workspace/url-parse
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch /workspace/dep.patch
node --require ./preload.js ./node_modules/.bin/mocha --grep "handles the case where the port is specified but empty"

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

cd /workspace/url-parse
git apply --whitespace=nowarn /workspace/fix.patch /workspace/dep.patch
node --require ./preload.js ./node_modules/.bin/mocha test/test.js


In Apache Airflow 2.3.0 through 2.3.4, there was an open redirect in the webserver's `/confirm` endpoint.

URL Redirection to Untrusted Site ('Open Redirect')

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

    def confirm(self):
        """Show confirmation page for marking tasks as success or failed."""
        args = request.args
        dag_id = args.get('dag_id')
        task_id = args.get('task_id')
        dag_run_id = args.get('dag_run_id')
        state = args.get('state')
        origin = args.get('origin')

        if 'map_index' not in args:
            map_indexes: list[int] | None = None
        else:
            map_indexes = args.getlist('map_index', type=int)

        upstream = to_boolean(args.get('upstream'))
        downstream = to_boolean(args.get('downstream'))
        future = to_boolean(args.get('future'))
        past = to_boolean(args.get('past'))
        origin = origin or url_for('Airflow.index')

        dag = get_airflow_app().dag_bag.get_dag(dag_id)
        if not dag:
            msg = f'DAG {dag_id} not found'
            return redirect_or_json(origin, msg, status='error', status_code=404)

        try:
            task = dag.get_task(task_id)
        except airflow.exceptions.TaskNotFound:
            msg = f"Task {task_id} not found"
            return redirect_or_json(origin, msg, status='error', status_code=404)

        task.dag = dag

        if state not in (
            'success',
            'failed',
        ):
            msg = f"Invalid state {state}, must be either 'success' or 'failed'"
            return redirect_or_json(origin, msg, status='error', status_code=400)

        latest_execution_date = dag.get_latest_execution_date()
        if not latest_execution_date:
            msg = f"Cannot mark tasks as {state}, seem that dag {dag_id} has never run"
            return redirect_or_json(origin, msg, status='error', status_code=400)

        if map_indexes is None:
            tasks: list[Operator] | list[tuple[Operator, int]] = [task]
        else:
            tasks = [(task, map_index) for map_index in map_indexes]

        to_be_altered = set_state(
            tasks=tasks,
            run_id=dag_run_id,
            upstream=upstream,
            downstream=downstream,
            future=future,
            past=past,
            state=state,
            commit=False,
        )

        if request.headers.get('Accept') == 'application/json':
            details = [str(t) for t in to_be_altered]
            return htmlsafe_json_dumps(details, separators=(',', ':'))

        details = "\n".join(str(t) for t in to_be_altered)

        response = self.render_template(
            "airflow/confirm.html",
            endpoint=url_for(f'Airflow.{state}'),
            message=f"Task instances you are about to mark as {state}:",
            details=details,
        )

        return response

    @expose('/failed', methods=['POST'])
    @auth.has_access(
        [
            (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG),
            (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_TASK_INSTANCE),
        ]
    )

    def confirm(self):
        """Show confirmation page for marking tasks as success or failed."""
        args = request.args
        dag_id = args.get('dag_id')
        task_id = args.get('task_id')
        dag_run_id = args.get('dag_run_id')
        state = args.get('state')
        origin = get_safe_url(args.get('origin'))

        if 'map_index' not in args:
            map_indexes: list[int] | None = None
        else:
            map_indexes = args.getlist('map_index', type=int)

        upstream = to_boolean(args.get('upstream'))
        downstream = to_boolean(args.get('downstream'))
        future = to_boolean(args.get('future'))
        past = to_boolean(args.get('past'))
        origin = origin or url_for('Airflow.index')

        dag = get_airflow_app().dag_bag.get_dag(dag_id)
        if not dag:
            msg = f'DAG {dag_id} not found'
            return redirect_or_json(origin, msg, status='error', status_code=404)

        try:
            task = dag.get_task(task_id)
        except airflow.exceptions.TaskNotFound:
            msg = f"Task {task_id} not found"
            return redirect_or_json(origin, msg, status='error', status_code=404)

        task.dag = dag

        if state not in (
            'success',
            'failed',
        ):
            msg = f"Invalid state {state}, must be either 'success' or 'failed'"
            return redirect_or_json(origin, msg, status='error', status_code=400)

        latest_execution_date = dag.get_latest_execution_date()
        if not latest_execution_date:
            msg = f"Cannot mark tasks as {state}, seem that dag {dag_id} has never run"
            return redirect_or_json(origin, msg, status='error', status_code=400)

        if map_indexes is None:
            tasks: list[Operator] | list[tuple[Operator, int]] = [task]
        else:
            tasks = [(task, map_index) for map_index in map_indexes]

        to_be_altered = set_state(
            tasks=tasks,
            run_id=dag_run_id,
            upstream=upstream,
            downstream=downstream,
            future=future,
            past=past,
            state=state,
            commit=False,
        )

        if request.headers.get('Accept') == 'application/json':
            details = [str(t) for t in to_be_altered]
            return htmlsafe_json_dumps(details, separators=(',', ':'))

        details = "\n".join(str(t) for t in to_be_altered)

        response = self.render_template(
            "airflow/confirm.html",
            endpoint=url_for(f'Airflow.{state}'),
            message=f"Task instances you are about to mark as {state}:",
            details=details,
        )

        return response

    @expose('/failed', methods=['POST'])
    @auth.has_access(
        [
            (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_DAG),
            (permissions.ACTION_CAN_EDIT, permissions.RESOURCE_TASK_INSTANCE),
        ]
    )

--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -5,7 +5,7 @@
         task_id = args.get('task_id')
         dag_run_id = args.get('dag_run_id')
         state = args.get('state')
-        origin = args.get('origin')
+        origin = get_safe_url(args.get('origin'))

         if 'map_index' not in args:
             map_indexes: list[int] | None = None


Nginx-UI is an online statistics for Server Indicators Monitor CPU usage, memory usage, load average, and disk usage in real-time. This issue may lead to information disclosure. By using `DefaultQuery`, the `"desc"` and `"id"` values are used as default values if the query parameters are not set. Thus, the `order` and `sort_by` query parameter are user-controlled and are being appended to the `order` variable without any sanitization. This issue has been patched in version 2.0.0.beta.9.

func (c *Ctx[T]) SortOrder() func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		sort := c.ctx.DefaultQuery("order", "desc")
		order := fmt.Sprintf("%s %s", DefaultQuery(c.ctx, "sort_by", c.itemKey), sort)
		return db.Order(order)
	}
}

func (c *Ctx[T]) OrderAndPaginate() func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		sort := c.ctx.DefaultQuery("order", "desc")

		order := fmt.Sprintf("%s %s", DefaultQuery(c.ctx, "sort_by", c.itemKey), sort)
		db = db.Order(order)

		_, offset, pageSize := GetPagingParams(c.ctx)

		return db.Offset(offset).Limit(pageSize)
	}
}

func OrderAndPaginate(c *gin.Context) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		sort := c.DefaultQuery("order", "desc")

		order := fmt.Sprintf("`%s` %s", DefaultQuery(c, "sort_by", "id"), sort)
		db = db.Order(order)

		page := cast.ToInt(c.Query("page"))
		if page == 0 {
			page = 1
		}
		pageSize := settings.ServerSettings.PageSize
		reqPageSize := c.Query("page_size")
		if reqPageSize != "" {
			pageSize = cast.ToInt(reqPageSize)
		}
		offset := (page - 1) * pageSize

		return db.Offset(offset).Limit(pageSize)
	}
}

func (c *Ctx[T]) SortOrder() func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		sort := c.ctx.DefaultQuery("order", "desc")
		if sort != "desc" && sort != "asc" {
			sort = "desc"
		}

		// check if the order field is valid
		// todo: maybe we can use more generic way to check if the sort_by is valid
		order := DefaultQuery(c.ctx, "sort_by", c.itemKey)
		s, _ := schema.Parse(c.Model, &sync.Map{}, schema.NamingStrategy{})
		if _, ok := s.FieldsByDBName[order]; ok {
			order = fmt.Sprintf("%s %s", order, sort)
			return db.Order(order)
		} else {
			logger.Error("invalid order field:", order)
		}

		return db
	}
}

}

func (c *Ctx[T]) OrderAndPaginate() func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		db = c.SortOrder()(db)
		_, offset, pageSize := GetPagingParams(c.ctx)
		return db.Offset(offset).Limit(pageSize)
	}
}

func OrderAndPaginate(c *gin.Context) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		sort := c.DefaultQuery("order", "desc")
		if sort != "desc" && sort != "asc" {
			sort = "desc"
		}

		// check if the order field is valid
		order := c.DefaultQuery("sort_by", "id")
		s, _ := schema.Parse(db.Model, &sync.Map{}, schema.NamingStrategy{})
		if _, ok := s.FieldsByName[order]; ok {
			order = fmt.Sprintf("%s %s", order, sort)
			db = db.Order(order)
		} else {
			logger.Error("invalid order field: ", order)
		}

		page := cast.ToInt(c.Query("page"))
		if page == 0 {
			page = 1
		}
		pageSize := settings.ServerSettings.PageSize
		reqPageSize := c.Query("page_size")
		if reqPageSize != "" {
			pageSize = cast.ToInt(reqPageSize)
		}
		offset := (page - 1) * pageSize

		return db.Offset(offset).Limit(pageSize)
	}
}

--- a/api/cosy/sort.go
+++ b/api/cosy/sort.go
@@ -1,7 +1,21 @@
 func (c *Ctx[T]) SortOrder() func(db *gorm.DB) *gorm.DB {
 	return func(db *gorm.DB) *gorm.DB {
 		sort := c.ctx.DefaultQuery("order", "desc")
-		order := fmt.Sprintf("%s %s", DefaultQuery(c.ctx, "sort_by", c.itemKey), sort)
-		return db.Order(order)
+		if sort != "desc" && sort != "asc" {
+			sort = "desc"
+		}
+
+		// check if the order field is valid
+		// todo: maybe we can use more generic way to check if the sort_by is valid
+		order := DefaultQuery(c.ctx, "sort_by", c.itemKey)
+		s, _ := schema.Parse(c.Model, &sync.Map{}, schema.NamingStrategy{})
+		if _, ok := s.FieldsByDBName[order]; ok {
+			order = fmt.Sprintf("%s %s", order, sort)
+			return db.Order(order)
+		} else {
+			logger.Error("invalid order field:", order)
+		}
+
+		return db
 	}
 }

--- a/api/cosy/sort.go
+++ b/api/cosy/sort.go
@@ -1,12 +1,9 @@
+}
+
 func (c *Ctx[T]) OrderAndPaginate() func(db *gorm.DB) *gorm.DB {
 	return func(db *gorm.DB) *gorm.DB {
-		sort := c.ctx.DefaultQuery("order", "desc")
-
-		order := fmt.Sprintf("%s %s", DefaultQuery(c.ctx, "sort_by", c.itemKey), sort)
-		db = db.Order(order)
-
+		db = c.SortOrder()(db)
 		_, offset, pageSize := GetPagingParams(c.ctx)
-
 		return db.Offset(offset).Limit(pageSize)
 	}
 }

--- a/model/model.go
+++ b/model/model.go
@@ -1,9 +1,19 @@
 func OrderAndPaginate(c *gin.Context) func(db *gorm.DB) *gorm.DB {
 	return func(db *gorm.DB) *gorm.DB {
 		sort := c.DefaultQuery("order", "desc")
+		if sort != "desc" && sort != "asc" {
+			sort = "desc"
+		}

-		order := fmt.Sprintf("`%s` %s", DefaultQuery(c, "sort_by", "id"), sort)
-		db = db.Order(order)
+		// check if the order field is valid
+		order := c.DefaultQuery("sort_by", "id")
+		s, _ := schema.Parse(db.Model, &sync.Map{}, schema.NamingStrategy{})
+		if _, ok := s.FieldsByName[order]; ok {
+			order = fmt.Sprintf("%s %s", order, sort)
+			db = db.Order(order)
+		} else {
+			logger.Error("invalid order field: ", order)
+		}

 		page := cast.ToInt(c.Query("page"))
 		if page == 0 {


HashiCorp Vault and Vault Enterprise versions 0.11.0 through 1.3.3 may, under certain circumstances, have existing nested-path policies grant access to Namespaces created after-the-fact. Fixed in 1.3.4.

Improper Authorization

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

Execution with Unnecessary Privileges

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

Improper Privilege Management

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

func (m *ExpirationManager) RenewToken(ctx context.Context, req *logical.Request, te *logical.TokenEntry,
	increment time.Duration) (*logical.Response, error) {
	defer metrics.MeasureSince([]string{"expire", "renew-token"}, time.Now())

	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core)
	if err != nil {
		return nil, err
	}
	if tokenNS == nil {
		return nil, namespace.ErrNoNamespace
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if ns.ID != tokenNS.ID {
		return nil, errors.New("cannot renew a token across namespaces")
	}

	// Compute the Lease ID
	saltedID, err := m.tokenStore.SaltID(ctx, te.ID)
	if err != nil {
		return nil, err
	}

	leaseID := path.Join(te.Path, saltedID)

	if ns.ID != namespace.RootNamespaceID {
		leaseID = fmt.Sprintf("%s.%s", leaseID, ns.ID)
	}

	// Load the entry
	le, err := m.loadEntry(ctx, leaseID)
	if err != nil {
		return nil, err
	}
	if le == nil {
		return logical.ErrorResponse("invalid lease ID"), logical.ErrInvalidRequest
	}

	// Check if the lease is renewable. Note that this also checks for a nil
	// lease and errors in that case as well.
	if _, err := le.renewable(); err != nil {
		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
	}

	// Attempt to renew the auth entry
	resp, err := m.renewAuthEntry(ctx, req, le, increment)
	if err != nil {
		return nil, err
	}
	if resp == nil {
		return nil, nil
	}
	if resp.IsError() {
		return &logical.Response{
			Data: resp.Data,
		}, nil
	}
	if resp.Auth == nil {
		return nil, nil
	}

	sysViewCtx := namespace.ContextWithNamespace(ctx, le.namespace)
	sysView := m.router.MatchingSystemView(sysViewCtx, le.Path)
	if sysView == nil {
		return nil, fmt.Errorf("unable to retrieve system view from router")
	}

	ttl, warnings, err := framework.CalculateTTL(sysView, increment, resp.Auth.TTL, resp.Auth.Period, resp.Auth.MaxTTL, resp.Auth.ExplicitMaxTTL, le.IssueTime)
	if err != nil {
		return nil, err
	}
	retResp := &logical.Response{}
	for _, warning := range warnings {
		retResp.AddWarning(warning)
	}
	resp.Auth.TTL = ttl

	// Attach the ClientToken
	resp.Auth.ClientToken = te.ID

	// Refresh groups
	if resp.Auth.EntityID != "" &&
		resp.Auth.GroupAliases != nil &&
		m.core.identityStore != nil {
		validAliases, err := m.core.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, resp.Auth.EntityID, resp.Auth.GroupAliases)
		if err != nil {
			return nil, err
		}
		resp.Auth.GroupAliases = validAliases
	}

	// Update the lease entry
	le.Auth = resp.Auth
	le.ExpireTime = resp.Auth.ExpirationTime()
	le.LastRenewalTime = time.Now()

	{
		m.pendingLock.Lock()
		if err := m.persistEntry(ctx, le); err != nil {
			m.pendingLock.Unlock()
			return nil, err
		}

		// Update the expiration time
		m.updatePendingInternal(le, resp.Auth.LeaseTotal())
		m.pendingLock.Unlock()
	}

	retResp.Auth = resp.Auth
	return retResp, nil
}

func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {
	defer metrics.MeasureSince([]string{"core", "handle_login_request"}, time.Now())

	req.Unauthenticated = true

	var nonHMACReqDataKeys []string
	entry := c.router.MatchingMountEntry(ctx, req.Path)
	if entry != nil {
		// Get and set ignored HMAC'd value.
		if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
			nonHMACReqDataKeys = rawVals.([]string)
		}
	}

	// Do an unauth check. This will cause EGP policies to be checked
	var auth *logical.Auth
	var ctErr error
	auth, _, ctErr = c.checkToken(ctx, req, true)
	if ctErr == logical.ErrPerfStandbyPleaseForward {
		return nil, nil, ctErr
	}
	if ctErr != nil {
		// If it is an internal error we return that, otherwise we
		// return invalid request so that the status codes can be correct
		var errType error
		switch ctErr {
		case ErrInternalError, logical.ErrPermissionDenied:
			errType = ctErr
		default:
			errType = logical.ErrInvalidRequest
		}

		logInput := &logical.LogInput{
			Auth:               auth,
			Request:            req,
			OuterErr:           ctErr,
			NonHMACReqDataKeys: nonHMACReqDataKeys,
		}
		if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
			c.logger.Error("failed to audit request", "path", req.Path, "error", err)
			return nil, nil, ErrInternalError
		}

		if errType != nil {
			retErr = multierror.Append(retErr, errType)
		}
		if ctErr == ErrInternalError {
			return nil, auth, retErr
		}
		return logical.ErrorResponse(ctErr.Error()), auth, retErr
	}

	// Create an audit trail of the request. Attach auth if it was returned,
	// e.g. if a token was provided.
	logInput := &logical.LogInput{
		Auth:               auth,
		Request:            req,
		NonHMACReqDataKeys: nonHMACReqDataKeys,
	}
	if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
		c.logger.Error("failed to audit request", "path", req.Path, "error", err)
		return nil, nil, ErrInternalError
	}

	// The token store uses authentication even when creating a new token,
	// so it's handled in handleRequest. It should not be reached here.
	if strings.HasPrefix(req.Path, "auth/token/") {
		c.logger.Error("unexpected login request for token backend", "request_path", req.Path)
		return nil, nil, ErrInternalError
	}

	// Route the request
	resp, routeErr := c.doRouting(ctx, req)
	if resp != nil {
		// If wrapping is used, use the shortest between the request and response
		var wrapTTL time.Duration
		var wrapFormat, creationPath string
		var sealWrap bool

		// Ensure no wrap info information is set other than, possibly, the TTL
		if resp.WrapInfo != nil {
			if resp.WrapInfo.TTL > 0 {
				wrapTTL = resp.WrapInfo.TTL
			}
			wrapFormat = resp.WrapInfo.Format
			creationPath = resp.WrapInfo.CreationPath
			sealWrap = resp.WrapInfo.SealWrap
			resp.WrapInfo = nil
		}

		if req.WrapInfo != nil {
			if req.WrapInfo.TTL > 0 {
				switch {
				case wrapTTL == 0:
					wrapTTL = req.WrapInfo.TTL
				case req.WrapInfo.TTL < wrapTTL:
					wrapTTL = req.WrapInfo.TTL
				}
			}
			if req.WrapInfo.Format != "" && wrapFormat == "" {
				wrapFormat = req.WrapInfo.Format
			}
		}

		if wrapTTL > 0 {
			resp.WrapInfo = &wrapping.ResponseWrapInfo{
				TTL:          wrapTTL,
				Format:       wrapFormat,
				CreationPath: creationPath,
				SealWrap:     sealWrap,
			}
		}
	}

	// A login request should never return a secret!
	if resp != nil && resp.Secret != nil {
		c.logger.Error("unexpected Secret response for login path", "request_path", req.Path)
		return nil, nil, ErrInternalError
	}

	// If the response generated an authentication, then generate the token
	if resp != nil && resp.Auth != nil {

		var entity *identity.Entity
		auth = resp.Auth

		mEntry := c.router.MatchingMountEntry(ctx, req.Path)

		if auth.Alias != nil &&
			mEntry != nil &&
			!mEntry.Local &&
			c.identityStore != nil {
			// Overwrite the mount type and mount path in the alias
			// information
			auth.Alias.MountType = req.MountType
			auth.Alias.MountAccessor = req.MountAccessor

			if auth.Alias.Name == "" {
				return nil, nil, fmt.Errorf("missing name in alias")
			}

			var err error

			// Fetch the entity for the alias, or create an entity if one
			// doesn't exist.
			entity, err = c.identityStore.CreateOrFetchEntity(ctx, auth.Alias)
			if err != nil {
				entity, err = possiblyForwardAliasCreation(ctx, c, err, auth, entity)
			}
			if err != nil {
				return nil, nil, err
			}
			if entity == nil {
				return nil, nil, fmt.Errorf("failed to create an entity for the authenticated alias")
			}

			if entity.Disabled {
				return nil, nil, logical.ErrPermissionDenied
			}

			auth.EntityID = entity.ID
			if auth.GroupAliases != nil {
				validAliases, err := c.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, auth.EntityID, auth.GroupAliases)
				if err != nil {
					return nil, nil, err
				}
				auth.GroupAliases = validAliases
			}
		}

		// Determine the source of the login
		source := c.router.MatchingMount(ctx, req.Path)
		source = strings.TrimPrefix(source, credentialRoutePrefix)
		source = strings.Replace(source, "/", "-", -1)

		// Prepend the source to the display name
		auth.DisplayName = strings.TrimSuffix(source+auth.DisplayName, "-")

		sysView := c.router.MatchingSystemView(ctx, req.Path)
		if sysView == nil {
			c.logger.Error("unable to look up sys view for login path", "request_path", req.Path)
			return nil, nil, ErrInternalError
		}

		tokenTTL, warnings, err := framework.CalculateTTL(sysView, 0, auth.TTL, auth.Period, auth.MaxTTL, auth.ExplicitMaxTTL, time.Time{})
		if err != nil {
			return nil, nil, err
		}
		for _, warning := range warnings {
			resp.AddWarning(warning)
		}

		ns, err := namespace.FromContext(ctx)
		if err != nil {
			return nil, nil, err
		}
		_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, ns, auth.EntityID)
		if err != nil {
			return nil, nil, ErrInternalError
		}

		auth.TokenPolicies = policyutil.SanitizePolicies(auth.Policies, !auth.NoDefaultPolicy)
		allPolicies := policyutil.SanitizePolicies(append(auth.TokenPolicies, identityPolicies[ns.ID]...), policyutil.DoNotAddDefaultPolicy)

		// Prevent internal policies from being assigned to tokens. We check
		// this on auth.Policies including derived ones from Identity before
		// actually making the token.
		for _, policy := range allPolicies {
			if policy == "root" {
				return logical.ErrorResponse("auth methods cannot create root tokens"), nil, logical.ErrInvalidRequest
			}
			if strutil.StrListContains(nonAssignablePolicies, policy) {
				return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil, logical.ErrInvalidRequest
			}
		}

		var registerFunc RegisterAuthFunc
		var funcGetErr error
		// Batch tokens should not be forwarded to perf standby
		if auth.TokenType == logical.TokenTypeBatch {
			registerFunc = c.RegisterAuth
		} else {
			registerFunc, funcGetErr = getAuthRegisterFunc(c)
		}
		if funcGetErr != nil {
			retErr = multierror.Append(retErr, funcGetErr)
			return nil, auth, retErr
		}

		err = registerFunc(ctx, tokenTTL, req.Path, auth)
		switch {
		case err == nil:
		case err == ErrInternalError:
			return nil, auth, err
		default:
			return logical.ErrorResponse(err.Error()), auth, logical.ErrInvalidRequest
		}

		auth.IdentityPolicies = policyutil.SanitizePolicies(identityPolicies[ns.ID], policyutil.DoNotAddDefaultPolicy)
		delete(identityPolicies, ns.ID)
		auth.ExternalNamespacePolicies = identityPolicies
		auth.Policies = allPolicies

		// Attach the display name, might be used by audit backends
		req.DisplayName = auth.DisplayName

	}

	return resp, auth, routeErr
}

func (m *ExpirationManager) RenewToken(ctx context.Context, req *logical.Request, te *logical.TokenEntry,
	increment time.Duration) (*logical.Response, error) {
	defer metrics.MeasureSince([]string{"expire", "renew-token"}, time.Now())

	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, m.core)
	if err != nil {
		return nil, err
	}
	if tokenNS == nil {
		return nil, namespace.ErrNoNamespace
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if ns.ID != tokenNS.ID {
		return nil, errors.New("cannot renew a token across namespaces")
	}

	// Compute the Lease ID
	saltedID, err := m.tokenStore.SaltID(ctx, te.ID)
	if err != nil {
		return nil, err
	}

	leaseID := path.Join(te.Path, saltedID)

	if ns.ID != namespace.RootNamespaceID {
		leaseID = fmt.Sprintf("%s.%s", leaseID, ns.ID)
	}

	// Load the entry
	le, err := m.loadEntry(ctx, leaseID)
	if err != nil {
		return nil, err
	}
	if le == nil {
		return logical.ErrorResponse("invalid lease ID"), logical.ErrInvalidRequest
	}

	// Check if the lease is renewable. Note that this also checks for a nil
	// lease and errors in that case as well.
	if _, err := le.renewable(); err != nil {
		return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
	}

	// Attempt to renew the auth entry
	resp, err := m.renewAuthEntry(ctx, req, le, increment)
	if err != nil {
		return nil, err
	}
	if resp == nil {
		return nil, nil
	}
	if resp.IsError() {
		return &logical.Response{
			Data: resp.Data,
		}, nil
	}
	if resp.Auth == nil {
		return nil, nil
	}

	sysViewCtx := namespace.ContextWithNamespace(ctx, le.namespace)
	sysView := m.router.MatchingSystemView(sysViewCtx, le.Path)
	if sysView == nil {
		return nil, fmt.Errorf("unable to retrieve system view from router")
	}

	ttl, warnings, err := framework.CalculateTTL(sysView, increment, resp.Auth.TTL, resp.Auth.Period, resp.Auth.MaxTTL, resp.Auth.ExplicitMaxTTL, le.IssueTime)
	if err != nil {
		return nil, err
	}
	retResp := &logical.Response{}
	for _, warning := range warnings {
		retResp.AddWarning(warning)
	}
	resp.Auth.TTL = ttl

	// Attach the ClientToken
	resp.Auth.ClientToken = te.ID

	// Refresh groups
	if resp.Auth.EntityID != "" && m.core.identityStore != nil {
		validAliases, err := m.core.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, resp.Auth.EntityID, resp.Auth.GroupAliases)
		if err != nil {
			return nil, err
		}
		resp.Auth.GroupAliases = validAliases
	}

	// Update the lease entry
	le.Auth = resp.Auth
	le.ExpireTime = resp.Auth.ExpirationTime()
	le.LastRenewalTime = time.Now()

	{
		m.pendingLock.Lock()
		if err := m.persistEntry(ctx, le); err != nil {
			m.pendingLock.Unlock()
			return nil, err
		}

		// Update the expiration time
		m.updatePendingInternal(le, resp.Auth.LeaseTotal())
		m.pendingLock.Unlock()
	}

	retResp.Auth = resp.Auth
	return retResp, nil
}

func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {
	defer metrics.MeasureSince([]string{"core", "handle_login_request"}, time.Now())

	req.Unauthenticated = true

	var nonHMACReqDataKeys []string
	entry := c.router.MatchingMountEntry(ctx, req.Path)
	if entry != nil {
		// Get and set ignored HMAC'd value.
		if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
			nonHMACReqDataKeys = rawVals.([]string)
		}
	}

	// Do an unauth check. This will cause EGP policies to be checked
	var auth *logical.Auth
	var ctErr error
	auth, _, ctErr = c.checkToken(ctx, req, true)
	if ctErr == logical.ErrPerfStandbyPleaseForward {
		return nil, nil, ctErr
	}
	if ctErr != nil {
		// If it is an internal error we return that, otherwise we
		// return invalid request so that the status codes can be correct
		var errType error
		switch ctErr {
		case ErrInternalError, logical.ErrPermissionDenied:
			errType = ctErr
		default:
			errType = logical.ErrInvalidRequest
		}

		logInput := &logical.LogInput{
			Auth:               auth,
			Request:            req,
			OuterErr:           ctErr,
			NonHMACReqDataKeys: nonHMACReqDataKeys,
		}
		if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
			c.logger.Error("failed to audit request", "path", req.Path, "error", err)
			return nil, nil, ErrInternalError
		}

		if errType != nil {
			retErr = multierror.Append(retErr, errType)
		}
		if ctErr == ErrInternalError {
			return nil, auth, retErr
		}
		return logical.ErrorResponse(ctErr.Error()), auth, retErr
	}

	// Create an audit trail of the request. Attach auth if it was returned,
	// e.g. if a token was provided.
	logInput := &logical.LogInput{
		Auth:               auth,
		Request:            req,
		NonHMACReqDataKeys: nonHMACReqDataKeys,
	}
	if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
		c.logger.Error("failed to audit request", "path", req.Path, "error", err)
		return nil, nil, ErrInternalError
	}

	// The token store uses authentication even when creating a new token,
	// so it's handled in handleRequest. It should not be reached here.
	if strings.HasPrefix(req.Path, "auth/token/") {
		c.logger.Error("unexpected login request for token backend", "request_path", req.Path)
		return nil, nil, ErrInternalError
	}

	// Route the request
	resp, routeErr := c.doRouting(ctx, req)
	if resp != nil {
		// If wrapping is used, use the shortest between the request and response
		var wrapTTL time.Duration
		var wrapFormat, creationPath string
		var sealWrap bool

		// Ensure no wrap info information is set other than, possibly, the TTL
		if resp.WrapInfo != nil {
			if resp.WrapInfo.TTL > 0 {
				wrapTTL = resp.WrapInfo.TTL
			}
			wrapFormat = resp.WrapInfo.Format
			creationPath = resp.WrapInfo.CreationPath
			sealWrap = resp.WrapInfo.SealWrap
			resp.WrapInfo = nil
		}

		if req.WrapInfo != nil {
			if req.WrapInfo.TTL > 0 {
				switch {
				case wrapTTL == 0:
					wrapTTL = req.WrapInfo.TTL
				case req.WrapInfo.TTL < wrapTTL:
					wrapTTL = req.WrapInfo.TTL
				}
			}
			if req.WrapInfo.Format != "" && wrapFormat == "" {
				wrapFormat = req.WrapInfo.Format
			}
		}

		if wrapTTL > 0 {
			resp.WrapInfo = &wrapping.ResponseWrapInfo{
				TTL:          wrapTTL,
				Format:       wrapFormat,
				CreationPath: creationPath,
				SealWrap:     sealWrap,
			}
		}
	}

	// A login request should never return a secret!
	if resp != nil && resp.Secret != nil {
		c.logger.Error("unexpected Secret response for login path", "request_path", req.Path)
		return nil, nil, ErrInternalError
	}

	// If the response generated an authentication, then generate the token
	if resp != nil && resp.Auth != nil {

		var entity *identity.Entity
		auth = resp.Auth

		mEntry := c.router.MatchingMountEntry(ctx, req.Path)

		if auth.Alias != nil &&
			mEntry != nil &&
			!mEntry.Local &&
			c.identityStore != nil {
			// Overwrite the mount type and mount path in the alias
			// information
			auth.Alias.MountType = req.MountType
			auth.Alias.MountAccessor = req.MountAccessor

			if auth.Alias.Name == "" {
				return nil, nil, fmt.Errorf("missing name in alias")
			}

			var err error

			// Fetch the entity for the alias, or create an entity if one
			// doesn't exist.
			entity, err = c.identityStore.CreateOrFetchEntity(ctx, auth.Alias)
			if err != nil {
				entity, err = possiblyForwardAliasCreation(ctx, c, err, auth, entity)
			}
			if err != nil {
				return nil, nil, err
			}
			if entity == nil {
				return nil, nil, fmt.Errorf("failed to create an entity for the authenticated alias")
			}

			if entity.Disabled {
				return nil, nil, logical.ErrPermissionDenied
			}

			auth.EntityID = entity.ID
			validAliases, err := c.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, auth.EntityID, auth.GroupAliases)
			if err != nil {
				return nil, nil, err
			}
			auth.GroupAliases = validAliases
		}

		// Determine the source of the login
		source := c.router.MatchingMount(ctx, req.Path)
		source = strings.TrimPrefix(source, credentialRoutePrefix)
		source = strings.Replace(source, "/", "-", -1)

		// Prepend the source to the display name
		auth.DisplayName = strings.TrimSuffix(source+auth.DisplayName, "-")

		sysView := c.router.MatchingSystemView(ctx, req.Path)
		if sysView == nil {
			c.logger.Error("unable to look up sys view for login path", "request_path", req.Path)
			return nil, nil, ErrInternalError
		}

		tokenTTL, warnings, err := framework.CalculateTTL(sysView, 0, auth.TTL, auth.Period, auth.MaxTTL, auth.ExplicitMaxTTL, time.Time{})
		if err != nil {
			return nil, nil, err
		}
		for _, warning := range warnings {
			resp.AddWarning(warning)
		}

		ns, err := namespace.FromContext(ctx)
		if err != nil {
			return nil, nil, err
		}
		_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, ns, auth.EntityID)
		if err != nil {
			return nil, nil, ErrInternalError
		}

		auth.TokenPolicies = policyutil.SanitizePolicies(auth.Policies, !auth.NoDefaultPolicy)
		allPolicies := policyutil.SanitizePolicies(append(auth.TokenPolicies, identityPolicies[ns.ID]...), policyutil.DoNotAddDefaultPolicy)

		// Prevent internal policies from being assigned to tokens. We check
		// this on auth.Policies including derived ones from Identity before
		// actually making the token.
		for _, policy := range allPolicies {
			if policy == "root" {
				return logical.ErrorResponse("auth methods cannot create root tokens"), nil, logical.ErrInvalidRequest
			}
			if strutil.StrListContains(nonAssignablePolicies, policy) {
				return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil, logical.ErrInvalidRequest
			}
		}

		var registerFunc RegisterAuthFunc
		var funcGetErr error
		// Batch tokens should not be forwarded to perf standby
		if auth.TokenType == logical.TokenTypeBatch {
			registerFunc = c.RegisterAuth
		} else {
			registerFunc, funcGetErr = getAuthRegisterFunc(c)
		}
		if funcGetErr != nil {
			retErr = multierror.Append(retErr, funcGetErr)
			return nil, auth, retErr
		}

		err = registerFunc(ctx, tokenTTL, req.Path, auth)
		switch {
		case err == nil:
		case err == ErrInternalError:
			return nil, auth, err
		default:
			return logical.ErrorResponse(err.Error()), auth, logical.ErrInvalidRequest
		}

		auth.IdentityPolicies = policyutil.SanitizePolicies(identityPolicies[ns.ID], policyutil.DoNotAddDefaultPolicy)
		delete(identityPolicies, ns.ID)
		auth.ExternalNamespacePolicies = identityPolicies
		auth.Policies = allPolicies

		// Attach the display name, might be used by audit backends
		req.DisplayName = auth.DisplayName

	}

	return resp, auth, routeErr
}

--- a/vault/expiration.go
+++ b/vault/expiration.go
@@ -82,9 +82,7 @@
 	resp.Auth.ClientToken = te.ID

 	// Refresh groups
-	if resp.Auth.EntityID != "" &&
-		resp.Auth.GroupAliases != nil &&
-		m.core.identityStore != nil {
+	if resp.Auth.EntityID != "" && m.core.identityStore != nil {
 		validAliases, err := m.core.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, resp.Auth.EntityID, resp.Auth.GroupAliases)
 		if err != nil {
 			return nil, err

--- a/vault/request_handling.go
+++ b/vault/request_handling.go
@@ -159,13 +159,11 @@
 			}

 			auth.EntityID = entity.ID
-			if auth.GroupAliases != nil {
-				validAliases, err := c.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, auth.EntityID, auth.GroupAliases)
-				if err != nil {
-					return nil, nil, err
-				}
-				auth.GroupAliases = validAliases
-			}
+			validAliases, err := c.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, auth.EntityID, auth.GroupAliases)
+			if err != nil {
+				return nil, nil, err
+			}
+			auth.GroupAliases = validAliases
 		}

 		// Determine the source of the login


PatrOwl is a free and open-source solution for orchestrating Security Operations. In versions prior to 1.7.7 PatrowlManager unrestrictly handle upload files in the findings import feature. This vulnerability is capable of uploading dangerous type of file to server leading to XSS attacks and potentially other forms of code injection. Users are advised to update to 1.7.7 as soon as possible. There are no known workarounds for this issue.

Unrestricted Upload of File with Dangerous Type

The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.

class ImportFindingsForm(forms.Form):
    class Meta:
        fields = ['engine', 'min_level', 'file']

    engine = forms.CharField(widget=forms.Select(
        attrs={'class': 'form-control form-control-sm'},
        choices=ENGINE_TYPES))
    min_level = forms.CharField(widget=forms.Select(
        attrs={'class': 'form-control form-control-sm'},
        choices=FINDING_SEVERITIES),
        label='Minimum severity')
    file = forms.FileField()

def validate_file_extension(value):
    ext = os.path.splitext(value.name)[1]
    valid_extensions = ['.xml', '.nessus', '.json']
    if ext not in valid_extensions:
        raise ValidationError(u'File not supported!')

class ImportFindingsForm(forms.Form):
    class Meta:
        fields = ['engine', 'min_level', 'file']

    engine = forms.CharField(widget=forms.Select(
        attrs={'class': 'form-control form-control-sm'},
        choices=ENGINE_TYPES))
    min_level = forms.CharField(widget=forms.Select(
        attrs={'class': 'form-control form-control-sm'},
        choices=FINDING_SEVERITIES),
        label='Minimum severity')
    file = forms.FileField(widget=forms.FileInput(
        attrs={'accept': 'text/xml,application/json'}),
        validators=[validate_file_extension]
    )

--- a/findings/forms.py
+++ b/findings/forms.py
@@ -1,3 +1,10 @@
+def validate_file_extension(value):
+    ext = os.path.splitext(value.name)[1]
+    valid_extensions = ['.xml', '.nessus', '.json']
+    if ext not in valid_extensions:
+        raise ValidationError(u'File not supported!')
+
+
 class ImportFindingsForm(forms.Form):
     class Meta:
         fields = ['engine', 'min_level', 'file']
@@ -9,4 +16,7 @@
         attrs={'class': 'form-control form-control-sm'},
         choices=FINDING_SEVERITIES),
         label='Minimum severity')
-    file = forms.FileField()
+    file = forms.FileField(widget=forms.FileInput(
+        attrs={'accept': 'text/xml,application/json'}),
+        validators=[validate_file_extension]
+    )


Unsanitized input in the default logger in github.com/gin-gonic/gin before v1.6.0 allows remote attackers to inject arbitrary log lines.

// defaultLogFormatter is the default log format function Logger middleware uses.
var defaultLogFormatter = func(param LogFormatterParams) string {
	var statusColor, methodColor, resetColor string
	if param.IsOutputColor() {
		statusColor = param.StatusCodeColor()
		methodColor = param.MethodColor()
		resetColor = param.ResetColor()
	}

	if param.Latency > time.Minute {
		// Truncate in a golang < 1.8 safe way
		param.Latency = param.Latency - param.Latency%time.Second
	}
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
		statusColor, param.StatusCode, resetColor,
		param.Latency,
		param.ClientIP,
		methodColor, param.Method, resetColor,
		param.Path,
		param.ErrorMessage,
	)
}

// defaultLogFormatter is the default log format function Logger middleware uses.
var defaultLogFormatter = func(param LogFormatterParams) string {
	var statusColor, methodColor, resetColor string
	if param.IsOutputColor() {
		statusColor = param.StatusCodeColor()
		methodColor = param.MethodColor()
		resetColor = param.ResetColor()
	}

	if param.Latency > time.Minute {
		// Truncate in a golang < 1.8 safe way
		param.Latency = param.Latency - param.Latency%time.Second
	}
	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
		statusColor, param.StatusCode, resetColor,
		param.Latency,
		param.ClientIP,
		methodColor, param.Method, resetColor,
		param.Path,
		param.ErrorMessage,
	)
}

--- a/logger.go
+++ b/logger.go
@@ -11,7 +11,7 @@
 		// Truncate in a golang < 1.8 safe way
 		param.Latency = param.Latency - param.Latency%time.Second
 	}
-	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
+	return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
 		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
 		statusColor, param.StatusCode, resetColor,
 		param.Latency,