Babel.Locale in Babel before 2.9.1 allows attackers to load arbitrary locale .dat files (containing serialized Python objects) via directory traversal, leading to code execution.

def exists(name):
    """Check whether locale data is available for the given locale.

    Returns `True` if it exists, `False` otherwise.

    :param name: the locale identifier string
    """
    if not name or not isinstance(name, string_types):
        return False
    if name in _cache:
        return True
    file_found = os.path.exists(os.path.join(_dirname, '%s.dat' % name))
    return True if file_found else bool(normalize_locale(name))

def locale_identifiers():
    """Return a list of all locale identifiers for which locale data is
    available.

    This data is cached after the first invocation in `locale_identifiers.cache`.

    Removing the `locale_identifiers.cache` attribute or setting it to `None`
    will cause this function to re-read the list from disk.

    .. versionadded:: 0.8.1

    :return: a list of locale identifiers (strings)
    """
    data = getattr(locale_identifiers, 'cache', None)
    if data is None:
        locale_identifiers.cache = data = [
            stem
            for stem, extension in
            (os.path.splitext(filename) for filename in os.listdir(_dirname))
            if extension == '.dat' and stem != 'root'
        ]
    return data

def load(name, merge_inherited=True):
    """Load the locale data for the given locale.

    The locale data is a dictionary that contains much of the data defined by
    the Common Locale Data Repository (CLDR). This data is stored as a
    collection of pickle files inside the ``babel`` package.

    >>> d = load('en_US')
    >>> d['languages']['sv']
    u'Swedish'

    Note that the results are cached, and subsequent requests for the same
    locale return the same dictionary:

    >>> d1 = load('en_US')
    >>> d2 = load('en_US')
    >>> d1 is d2
    True

    :param name: the locale identifier string (or "root")
    :param merge_inherited: whether the inherited data should be merged into
                            the data of the requested locale
    :raise `IOError`: if no locale data file is found for the given locale
                      identifer, or one of the locales it inherits from
    """
    _cache_lock.acquire()
    try:
        data = _cache.get(name)
        if not data:
            # Load inherited data
            if name == 'root' or not merge_inherited:
                data = {}
            else:
                from babel.core import get_global
                parent = get_global('parent_exceptions').get(name)
                if not parent:
                    parts = name.split('_')
                    if len(parts) == 1:
                        parent = 'root'
                    else:
                        parent = '_'.join(parts[:-1])
                data = load(parent).copy()
            filename = os.path.join(_dirname, '%s.dat' % name)
            with open(filename, 'rb') as fileobj:
                if name != 'root' and merge_inherited:
                    merge(data, pickle.load(fileobj))
                else:
                    data = pickle.load(fileobj)
            _cache[name] = data
        return data
    finally:
        _cache_lock.release()

def exists(name):
    """Check whether locale data is available for the given locale.

    Returns `True` if it exists, `False` otherwise.

    :param name: the locale identifier string
    """
    if not name or not isinstance(name, string_types):
        return False
    if name in _cache:
        return True
    file_found = os.path.exists(resolve_locale_filename(name))
    return True if file_found else bool(normalize_locale(name))

def locale_identifiers():
    """Return a list of all locale identifiers for which locale data is
    available.

    This data is cached after the first invocation in `locale_identifiers.cache`.

    Removing the `locale_identifiers.cache` attribute or setting it to `None`
    will cause this function to re-read the list from disk.

    .. versionadded:: 0.8.1

    :return: a list of locale identifiers (strings)
    """
    data = getattr(locale_identifiers, 'cache', None)
    if data is None:
        locale_identifiers.cache = data = [
            stem
            for stem, extension in
            (os.path.splitext(filename) for filename in os.listdir(_dirname))
            if extension == '.dat' and stem != 'root'
        ]
    return data

def load(name, merge_inherited=True):
    """Load the locale data for the given locale.

    The locale data is a dictionary that contains much of the data defined by
    the Common Locale Data Repository (CLDR). This data is stored as a
    collection of pickle files inside the ``babel`` package.

    >>> d = load('en_US')
    >>> d['languages']['sv']
    u'Swedish'

    Note that the results are cached, and subsequent requests for the same
    locale return the same dictionary:

    >>> d1 = load('en_US')
    >>> d2 = load('en_US')
    >>> d1 is d2
    True

    :param name: the locale identifier string (or "root")
    :param merge_inherited: whether the inherited data should be merged into
                            the data of the requested locale
    :raise `IOError`: if no locale data file is found for the given locale
                      identifer, or one of the locales it inherits from
    """
    name = os.path.basename(name)
    _cache_lock.acquire()
    try:
        data = _cache.get(name)
        if not data:
            # Load inherited data
            if name == 'root' or not merge_inherited:
                data = {}
            else:
                from babel.core import get_global
                parent = get_global('parent_exceptions').get(name)
                if not parent:
                    parts = name.split('_')
                    if len(parts) == 1:
                        parent = 'root'
                    else:
                        parent = '_'.join(parts[:-1])
                data = load(parent).copy()
            filename = resolve_locale_filename(name)
            with open(filename, 'rb') as fileobj:
                if name != 'root' and merge_inherited:
                    merge(data, pickle.load(fileobj))
                else:
                    data = pickle.load(fileobj)
            _cache[name] = data
        return data
    finally:
        _cache_lock.release()

def resolve_locale_filename(name):
    """
    Resolve a locale identifier to a `.dat` path on disk.
    """

    # Clean up any possible relative paths.
    name = os.path.basename(name)

    # Ensure we're not left with one of the Windows reserved names.
    if sys.platform == "win32" and _windows_reserved_name_re.match(os.path.splitext(name)[0]):
        raise ValueError("Name %s is invalid on Windows" % name)

    # Build the path.
    return os.path.join(_dirname, '%s.dat' % name)

--- a/babel/localedata.py
+++ b/babel/localedata.py
@@ -9,5 +9,5 @@
         return False
     if name in _cache:
         return True
-    file_found = os.path.exists(os.path.join(_dirname, '%s.dat' % name))
+    file_found = os.path.exists(resolve_locale_filename(name))
     return True if file_found else bool(normalize_locale(name))

--- a/babel/localedata.py
+++ b/babel/localedata.py
@@ -47,6 +47,7 @@
     :raise `IOError`: if no locale data file is found for the given locale
                       identifer, or one of the locales it inherits from
     """
+    name = os.path.basename(name)
     _cache_lock.acquire()
     try:
         data = _cache.get(name)
@@ -64,7 +65,7 @@
                     else:
                         parent = '_'.join(parts[:-1])
                 data = load(parent).copy()
-            filename = os.path.join(_dirname, '%s.dat' % name)
+            filename = resolve_locale_filename(name)
             with open(filename, 'rb') as fileobj:
                 if name != 'root' and merge_inherited:
                     merge(data, pickle.load(fileobj))

--- /dev/null
+++ b/babel/localedata.py
@@ -0,0 +1,15 @@
+def resolve_locale_filename(name):
+    """
+    Resolve a locale identifier to a `.dat` path on disk.
+    """
+
+    # Clean up any possible relative paths.
+    name = os.path.basename(name)
+
+    # Ensure we're not left with one of the Windows reserved names.
+    if sys.platform == "win32" and _windows_reserved_name_re.match(os.path.splitext(name)[0]):
+        raise ValueError("Name %s is invalid on Windows" % name)
+
+    # Build the path.
+    return os.path.join(_dirname, '%s.dat' % name)
+

--- /dev/null
+++ b/babel/localedata.py
@@ -0,0 +1 @@
+_windows_reserved_name_re = re.compile("^(con|prn|aux|nul|com[0-9]|lpt[0-9])$", re.I)


Open OnDemand is an open-source HPC portal. Users can flood logs by interacting with the shell app and generating many errors. Users who flood logs can create very large log files causing a Denial of Service (DoS) to the ondemand system. This vulnerability is fixed in 3.1.14 and 4.0.6.

Uncontrolled Resource Consumption

The product does not properly control the allocation and maintenance of a limited resource.

wss.on('connection', function connection (ws, req) {
  var dir,
      term,
      args,
      host,
      cmd = process.env.OOD_SSH_WRAPPER || 'ssh';

  ws.isAlive = true;
  ws.startedAt = Date.now();
  ws.lastActivity = Date.now();

  console.log('Connection established');

  [host, dir] = host_and_dir_from_url(req.url);

  // Verify authentication
  token = req.url.match(/csrf=([^&]*)/)[1];
  authError = detect_auth_error(token, req.origin, custom_server_origin(default_server_origin(req)), host);
  if (authError) {
    // 3146 has no meaning, any number between 3000-3999 is fair to use
    ws.close(3146, authError);
  } else {
    args = dir ? [host, '-t', 'cd \'' + dir.replace(/\'/g, "'\\''") + '\' ; exec ${SHELL} -l'] : [host];

    process.env.LANG = 'en_US.UTF-8'; // this patch (from b996d36) lost when removing wetty (2c8a022)

    term = pty.spawn(cmd, args, {
      name: 'xterm-16color',
      cols: 80,
      rows: 30
    });

    console.log('Opened terminal: ' + term.pid);

    term.onData(function (data) {
      ws.send(data, function (error) {
        if (error) console.log('Send error: ' + error.message);
      });
      ws.lastActivity = Date.now();
    });

    term.onExit(function (_exitData) {
      ws.close();
    });

    ws.on('message', function (msg) {
      msg = JSON.parse(msg);
      if (msg.input)  {
        term.write(msg.input);
        this.lastActivity = Date.now();
      }
      if (msg.resize) term.resize(parseInt(msg.resize.cols), parseInt(msg.resize.rows));
    });

    ws.on('close', function () {
      term.end();
      this.isAlive = false;
      console.log('Closed terminal: ' + term.pid);
    });

    ws.on('pong', function () {
      this.isAlive = true;
    });
  }
});

wss.on('connection', function connection (ws, req) {
  var dir,
      term,
      args,
      host,
      cmd = process.env.OOD_SSH_WRAPPER || 'ssh';

  ws.isAlive = true;
  ws.startedAt = Date.now();
  ws.lastActivity = Date.now();
  ws.logger = createLogger();

  console.log('Connection established');

  [host, dir] = host_and_dir_from_url(req.url);

  // Verify authentication
  token = req.url.match(/csrf=([^&]*)/)[1];
  authError = detect_auth_error(token, req.origin, custom_server_origin(default_server_origin(req)), host);
  if (authError) {
    // 3146 has no meaning, any number between 3000-3999 is fair to use
    ws.close(3146, authError);
  } else {
    args = dir ? [host, '-t', 'cd \'' + dir.replace(/\'/g, "'\\''") + '\' ; exec ${SHELL} -l'] : [host];

    process.env.LANG = 'en_US.UTF-8'; // this patch (from b996d36) lost when removing wetty (2c8a022)

    term = pty.spawn(cmd, args, {
      name: 'xterm-16color',
      cols: 80,
      rows: 30
    });

    console.log('Opened terminal: ' + term.pid);

    term.onData(function (data) {
      ws.send(data, function (error) {
        if(ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
          ws.logger.log('The websocket will not receive any more messages. Killing the terminal connection');
          term.kill();
        } else if (error) {
          ws.logger.log('Send error: ' + error.message);
        }
      });
      ws.lastActivity = Date.now();
    });

    term.onExit(function (_exitData) {
      ws.close();
    });

    ws.on('message', function (msg) {
      msg = JSON.parse(msg);
      if (msg.input)  {
        term.write(msg.input);
        this.lastActivity = Date.now();
      }
      if (msg.resize) term.resize(parseInt(msg.resize.cols), parseInt(msg.resize.rows));
    });

    ws.on('close', function () {
      term.end();
      this.isAlive = false;
      console.log('Closed terminal: ' + term.pid);
      ws.logger.flush();
    });

    ws.on('pong', function () {
      this.isAlive = true;
    });
  }
});

function createLogger() {
  // Combine logs for logInterval ms duration
  const logInterval = 5000;
  const messages = [];
  let lastLog = 0;
  let timer;

  const logQueuedMessages = (immediate = false) => {
    const now = Date.now();
    clearTimeout(timer);
    // Nothing logged since logInterval, log immediately
    if (now - lastLog > logInterval || immediate) {
      for (const { message, count } of messages) {
        console.log(message);
        if (count > 1) {
          console.log(`Skipped ${count-1} previous duplicated messages`);
        }
      }
      messages.length = 0;
      lastLog = now;
    } else if (messages.length > 0) {
      // Log at most logInterval duration since current queue started
      timer = setTimeout(logQueuedMessages, (lastLog + logInterval - now));
    }
  }

  return {
    log: (msg) => {
      const lastMessage = messages.at(-1);
      if (lastMessage && lastMessage.message == msg) {
        lastMessage.count++;
      } else {
        messages.push({"message": msg, count: 1});
      }
      logQueuedMessages();
    },
    flush: () => logQueuedMessages(true),
  };
};

--- a/apps/shell/app.js
+++ b/apps/shell/app.js
@@ -8,7 +8,8 @@
   ws.isAlive = true;
   ws.startedAt = Date.now();
   ws.lastActivity = Date.now();
-
+  ws.logger = createLogger();
+
   console.log('Connection established');

   [host, dir] = host_and_dir_from_url(req.url);
@@ -35,7 +36,12 @@

     term.onData(function (data) {
       ws.send(data, function (error) {
-        if (error) console.log('Send error: ' + error.message);
+        if(ws.readyState === WebSocket.CLOSED || ws.readyState === WebSocket.CLOSING) {
+          ws.logger.log('The websocket will not receive any more messages. Killing the terminal connection');
+          term.kill();
+        } else if (error) {
+          ws.logger.log('Send error: ' + error.message);
+        }
       });
       ws.lastActivity = Date.now();
     });
@@ -57,6 +63,7 @@
       term.end();
       this.isAlive = false;
       console.log('Closed terminal: ' + term.pid);
+      ws.logger.flush();
     });

     ws.on('pong', function () {

--- /dev/null
+++ b/apps/shell/app.js
@@ -0,0 +1,39 @@
+function createLogger() {
+  // Combine logs for logInterval ms duration
+  const logInterval = 5000;
+  const messages = [];
+  let lastLog = 0;
+  let timer;
+
+  const logQueuedMessages = (immediate = false) => {
+    const now = Date.now();
+    clearTimeout(timer);
+    // Nothing logged since logInterval, log immediately
+    if (now - lastLog > logInterval || immediate) {
+      for (const { message, count } of messages) {
+        console.log(message);
+        if (count > 1) {
+          console.log(`Skipped ${count-1} previous duplicated messages`);
+        }
+      }
+      messages.length = 0;
+      lastLog = now;
+    } else if (messages.length > 0) {
+      // Log at most logInterval duration since current queue started
+      timer = setTimeout(logQueuedMessages, (lastLog + logInterval - now));
+    }
+  }
+
+  return {
+    log: (msg) => {
+      const lastMessage = messages.at(-1);
+      if (lastMessage && lastMessage.message == msg) {
+        lastMessage.count++;
+      } else {
+        messages.push({"message": msg, count: 1});
+      }
+      logQueuedMessages();
+    },
+    flush: () => logQueuedMessages(true),
+  };
+};


In "index.js" file line 240, the run command executes the git command with a user controlled variable called remoteUrl. This affects git-diff-apply all versions prior to 0.22.2.

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

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

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

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

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

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

module.exports = async function gitDiffApply({
  cwd = process.cwd(),
  remoteUrl,
  startTag,
  endTag,
  resolveConflicts: _resolveConflicts,
  ignoredFiles = [],
  reset,
  init,
  createCustomDiff,
  startCommand,
  endCommand
}) {
  let _tmpDir;
  let tmpWorkingDir;

  let hasConflicts;
  let returnObject;

  let isCodeUntracked;
  let isCodeModified;

  let root;

  let err;

  async function buildReturnObject() {
    let from;

    if (reset || init) {
      from = {};
    } else {
      await checkOutTag(startTag, { cwd: _tmpDir });

      from = convertToObj(_tmpDir, ignoredFiles);
    }

    await checkOutTag(endTag, { cwd: _tmpDir });

    let to = convertToObj(_tmpDir, ignoredFiles);

    return {
      from,
      to
    };
  }

  async function namespaceRepoWithSubDir(subDir) {
    let newTmpDir = await tmpDir();

    await gitInit({ cwd: newTmpDir });

    let newTmpSubDir = path.join(newTmpDir, subDir);

    async function copyToSubDir(tag) {
      await ensureDir(newTmpSubDir);

      await checkOutTag(tag, { cwd: _tmpDir });

      await utils.copy(_tmpDir, newTmpSubDir);

      await commitAndTag(tag, { cwd: newTmpDir });
    }

    if (!(reset || init)) {
      await copyToSubDir(startTag);

      await gitRemoveAll({ cwd: newTmpDir });
    }

    await copyToSubDir(endTag);

    _tmpDir = newTmpDir;
    tmpWorkingDir = newTmpSubDir;
  }

  async function copy() {
    await utils.copy(tmpWorkingDir, cwd);
  }

  async function resetIgnoredFiles(cwd) {
    for (let ignoredFile of ignoredFiles) {
      // An exist check is not good enough.
      // `git checkout` will fail unless it is also tracked.
      let isTracked = await utils.run(`git ls-files ${ignoredFile}`, { cwd });
      if (isTracked) {
        await utils.run(`git checkout -- ${ignoredFile}`, { cwd });
      } else {
        await fs.remove(path.join(cwd, ignoredFile));
      }
    }
  }

  async function createPatchFile() {
    let patchFile = path.join(await tmpDir(), 'file.patch');
    await utils.run(`git diff ${startTag} ${endTag} --binary > ${patchFile}`, { cwd: _tmpDir });
    if (await fs.readFile(patchFile, 'utf8') !== '') {
      return patchFile;
    }
  }

  async function applyPatch(patchFile) {
    // --whitespace=fix seems to prevent any unnecessary conflicts with line endings
    // https://stackoverflow.com/questions/6308625/how-to-avoid-git-apply-changing-line-endings#comment54419617_11189296
    await utils.run(`git apply --whitespace=fix ${patchFile}`, { cwd: _tmpDir });
  }

  async function go() {
    if (reset || init) {
      await checkOutTag(endTag, { cwd: _tmpDir });

      isCodeUntracked = true;
      isCodeModified = true;
      if (reset) {
        await utils.gitRemoveAll({ cwd: root });
      }

      await copy();

      await utils.run('git reset', { cwd });

      await resetIgnoredFiles(cwd);

      return;
    }

    await checkOutTag(startTag, { cwd: _tmpDir });

    await utils.run(`git branch ${tempBranchName}`, { cwd: _tmpDir });
    await utils.run(`git checkout ${tempBranchName}`, { cwd: _tmpDir });

    let patchFile = await createPatchFile();
    if (!patchFile) {
      return;
    }

    await applyPatch(patchFile);

    await resetIgnoredFiles(tmpWorkingDir);

    let wereAnyChanged = !await isGitClean({ cwd: _tmpDir });

    if (wereAnyChanged) {
      await commit({ cwd: _tmpDir });

      let sha = await utils.run('git rev-parse HEAD', { cwd: _tmpDir });

      await utils.run(`git remote add ${tempBranchName} ${_tmpDir}`, { cwd });
      await utils.run(`git fetch --no-tags ${tempBranchName}`, { cwd });

      try {
        await utils.run(`git cherry-pick --no-commit ${sha.trim()}`, { cwd });
      } catch (err) {
        hasConflicts = true;
      }

      await utils.run(`git remote remove ${tempBranchName}`, { cwd });
    }
  }

  try {
    if (startTag === endTag && !(reset || init)) {
      throw 'Tags match, nothing to apply';
    }

    let isClean;

    try {
      isClean = await isGitClean({ cwd });
    } catch (err) {
      throw 'Not a git repository';
    }

    if (!isClean) {
      throw 'You must start with a clean working directory';
    }

    if (createCustomDiff) {
      let tmpPath = await createCustomRemote({
        startCommand,
        endCommand,
        startTag,
        endTag,
        reset,
        init
      });

      remoteUrl = tmpPath;
    }

    _tmpDir = await tmpDir();
    tmpWorkingDir = _tmpDir;

    await utils.run(`git clone ${remoteUrl} ${_tmpDir}`);

    // needed because we are going to be committing in here
    await gitConfigInit({ cwd: _tmpDir });

    returnObject = await buildReturnObject();

    root = await getRootDir({ cwd });
    let subDir = await getSubDir({ cwd });
    if (subDir) {
      debug('subDir', subDir);

      await namespaceRepoWithSubDir(subDir);
    }

    await go();

    debug('success');
  } catch (_err) {
    err = _err;

    try {
      if (isCodeUntracked) {
        await utils.run('git clean -f', { cwd });
      }
      if (isCodeModified) {
        await utils.run('git reset --hard', { cwd });
      }
    } catch (err2) {
      throw {
        err,
        err2
      };
    }
  }

  if (err) {
    debug('failure');

    throw err;
  }

  if (hasConflicts && _resolveConflicts) {
    returnObject.resolveConflictsProcess = resolveConflicts({ cwd });
  }

  return returnObject;
};

const gitRemoveAll = require('./git-remove-all');
const createCustomRemote = require('./create-custom-remote');
const { runWithSpawn } = require('./run');

module.exports = async function gitDiffApply({
  cwd = process.cwd(),
  remoteUrl,
  startTag,
  endTag,
  resolveConflicts: _resolveConflicts,
  ignoredFiles = [],
  reset,
  init,
  createCustomDiff,
  startCommand,
  endCommand
}) {
  let _tmpDir;
  let tmpWorkingDir;

  let hasConflicts;
  let returnObject;

  let isCodeUntracked;
  let isCodeModified;

  let root;

  let err;

  async function buildReturnObject() {
    let from;

    if (reset || init) {
      from = {};
    } else {
      await checkOutTag(startTag, { cwd: _tmpDir });

      from = convertToObj(_tmpDir, ignoredFiles);
    }

    await checkOutTag(endTag, { cwd: _tmpDir });

    let to = convertToObj(_tmpDir, ignoredFiles);

    return {
      from,
      to
    };
  }

  async function namespaceRepoWithSubDir(subDir) {
    let newTmpDir = await tmpDir();

    await gitInit({ cwd: newTmpDir });

    let newTmpSubDir = path.join(newTmpDir, subDir);

    async function copyToSubDir(tag) {
      await ensureDir(newTmpSubDir);

      await checkOutTag(tag, { cwd: _tmpDir });

      await utils.copy(_tmpDir, newTmpSubDir);

      await commitAndTag(tag, { cwd: newTmpDir });
    }

    if (!(reset || init)) {
      await copyToSubDir(startTag);

      await gitRemoveAll({ cwd: newTmpDir });
    }

    await copyToSubDir(endTag);

    _tmpDir = newTmpDir;
    tmpWorkingDir = newTmpSubDir;
  }

  async function copy() {
    await utils.copy(tmpWorkingDir, cwd);
  }

  async function resetIgnoredFiles(cwd) {
    for (let ignoredFile of ignoredFiles) {
      // An exist check is not good enough.
      // `git checkout` will fail unless it is also tracked.
      let isTracked = await utils.run(`git ls-files ${ignoredFile}`, { cwd });
      if (isTracked) {
        await utils.run(`git checkout -- ${ignoredFile}`, { cwd });
      } else {
        await fs.remove(path.join(cwd, ignoredFile));
      }
    }
  }

  async function createPatchFile() {
    let patchFile = path.join(await tmpDir(), 'file.patch');
    await utils.run(`git diff ${startTag} ${endTag} --binary > ${patchFile}`, { cwd: _tmpDir });
    if (await fs.readFile(patchFile, 'utf8') !== '') {
      return patchFile;
    }
  }

  async function applyPatch(patchFile) {
    // --whitespace=fix seems to prevent any unnecessary conflicts with line endings
    // https://stackoverflow.com/questions/6308625/how-to-avoid-git-apply-changing-line-endings#comment54419617_11189296
    await utils.run(`git apply --whitespace=fix ${patchFile}`, { cwd: _tmpDir });
  }

  async function go() {
    if (reset || init) {
      await checkOutTag(endTag, { cwd: _tmpDir });

      isCodeUntracked = true;
      isCodeModified = true;
      if (reset) {
        await utils.gitRemoveAll({ cwd: root });
      }

      await copy();

      await utils.run('git reset', { cwd });

      await resetIgnoredFiles(cwd);

      return;
    }

    await checkOutTag(startTag, { cwd: _tmpDir });

    await utils.run(`git branch ${tempBranchName}`, { cwd: _tmpDir });
    await utils.run(`git checkout ${tempBranchName}`, { cwd: _tmpDir });

    let patchFile = await createPatchFile();
    if (!patchFile) {
      return;
    }

    await applyPatch(patchFile);

    await resetIgnoredFiles(tmpWorkingDir);

    let wereAnyChanged = !await isGitClean({ cwd: _tmpDir });

    if (wereAnyChanged) {
      await commit({ cwd: _tmpDir });

      let sha = await utils.run('git rev-parse HEAD', { cwd: _tmpDir });

      await utils.run(`git remote add ${tempBranchName} ${_tmpDir}`, { cwd });
      await utils.run(`git fetch --no-tags ${tempBranchName}`, { cwd });

      try {
        await utils.run(`git cherry-pick --no-commit ${sha.trim()}`, { cwd });
      } catch (err) {
        hasConflicts = true;
      }

      await utils.run(`git remote remove ${tempBranchName}`, { cwd });
    }
  }

  try {
    if (startTag === endTag && !(reset || init)) {
      throw 'Tags match, nothing to apply';
    }

    let isClean;

    try {
      isClean = await isGitClean({ cwd });
    } catch (err) {
      throw 'Not a git repository';
    }

    if (!isClean) {
      throw 'You must start with a clean working directory';
    }

    if (createCustomDiff) {
      let tmpPath = await createCustomRemote({
        startCommand,
        endCommand,
        startTag,
        endTag,
        reset,
        init
      });

      remoteUrl = tmpPath;
    }

    _tmpDir = await tmpDir();
    tmpWorkingDir = _tmpDir;

    await runWithSpawn('git', ['clone', remoteUrl, _tmpDir]);

    // needed because we are going to be committing in here
    await gitConfigInit({ cwd: _tmpDir });

    returnObject = await buildReturnObject();

    root = await getRootDir({ cwd });
    let subDir = await getSubDir({ cwd });
    if (subDir) {
      debug('subDir', subDir);

      await namespaceRepoWithSubDir(subDir);
    }

    await go();

    debug('success');
  } catch (_err) {
    err = _err;

    try {
      if (isCodeUntracked) {
        await utils.run('git clean -f', { cwd });
      }
      if (isCodeModified) {
        await utils.run('git reset --hard', { cwd });
      }
    } catch (err2) {
      throw {
        err,
        err2
      };
    }
  }

  if (err) {
    debug('failure');

    throw err;
  }

  if (hasConflicts && _resolveConflicts) {
    returnObject.resolveConflictsProcess = resolveConflicts({ cwd });
  }

  return returnObject;
};

--- a/src/index.js
+++ b/src/index.js
@@ -1,2 +1,3 @@
 const gitRemoveAll = require('./git-remove-all');
 const createCustomRemote = require('./create-custom-remote');
+const { runWithSpawn } = require('./run');

--- a/src/index.js
+++ b/src/index.js
@@ -191,7 +191,7 @@
     _tmpDir = await tmpDir();
     tmpWorkingDir = _tmpDir;

-    await utils.run(`git clone ${remoteUrl} ${_tmpDir}`);
+    await runWithSpawn('git', ['clone', remoteUrl, _tmpDir]);

     // needed because we are going to be committing in here
     await gitConfigInit({ cwd: _tmpDir });


treq is an HTTP library inspired by requests but written on top of Twisted's Agents. Treq's request methods (`treq.get`, `treq.post`, etc.) and `treq.client.HTTPClient` constructor accept cookies as a dictionary. Such cookies are not bound to a single domain, and are therefore sent to *every* domain ("supercookies"). This can potentially cause sensitive information to leak upon an HTTP redirect to a different domain., e.g. should `https://example.com` redirect to `http://cloudstorageprovider.com` the latter will receive the cookie `session`. Treq 2021.1.0 and later bind cookies given to request methods (`treq.request`, `treq.get`, `HTTPClient.request`, `HTTPClient.get`, etc.) to the origin of the *url* parameter. Users are advised to upgrade. For users unable to upgrade Instead of passing a dictionary as the *cookies* argument, pass a `http.cookiejar.CookieJar` instance with properly domain- and scheme-scoped cookies in it.

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 __init__(self, agent, cookiejar=None,
                 data_to_body_producer=IBodyProducer):
        self._agent = agent
        self._cookiejar = cookiejar or cookiejar_from_dict({})
        self._data_to_body_producer = data_to_body_producer

    def request(
        self,
        method,
        url,
        *,
        params=None,
        headers=None,
        data=None,
        files=None,
        json=_NOTHING,
        auth=None,
        cookies=None,
        allow_redirects=True,
        browser_like_redirects=False,
        unbuffered=False,
        reactor=None,
        timeout=None,
        _stacklevel=2,
    ):
        """
        See :func:`treq.request()`.
        """
        method = method.encode('ascii').upper()

        if isinstance(url, DecodedURL):
            parsed_url = url.encoded_url
        elif isinstance(url, EncodedURL):
            parsed_url = url
        elif isinstance(url, str):
            # We use hyperlink in lazy mode so that users can pass arbitrary
            # bytes in the path and querystring.
            parsed_url = EncodedURL.from_text(url)
        else:
            parsed_url = EncodedURL.from_text(url.decode('ascii'))

        # Join parameters provided in the URL
        # and the ones passed as argument.
        if params:
            parsed_url = parsed_url.replace(
                query=parsed_url.query + tuple(_coerced_query_params(params))
            )

        url = parsed_url.to_uri().to_text().encode('ascii')

        headers = self._request_headers(headers, _stacklevel + 1)

        bodyProducer, contentType = self._request_body(data, files, json,
                                                       stacklevel=_stacklevel + 1)
        if contentType is not None:
            headers.setRawHeaders(b'Content-Type', [contentType])

        if not isinstance(cookies, CookieJar):
            cookies = cookiejar_from_dict(cookies)

        cookies = merge_cookies(self._cookiejar, cookies)
        wrapped_agent = CookieAgent(self._agent, cookies)

        if allow_redirects:
            if browser_like_redirects:
                wrapped_agent = BrowserLikeRedirectAgent(wrapped_agent)
            else:
                wrapped_agent = RedirectAgent(wrapped_agent)

        wrapped_agent = ContentDecoderAgent(wrapped_agent,
                                            [(b'gzip', GzipDecoder)])

        if auth:
            wrapped_agent = add_auth(wrapped_agent, auth)

        d = wrapped_agent.request(
            method, url, headers=headers,
            bodyProducer=bodyProducer)

        if reactor is None:
            from twisted.internet import reactor
        if timeout:
            delayedCall = reactor.callLater(timeout, d.cancel)

            def gotResult(result):
                if delayedCall.active():
                    delayedCall.cancel()
                return result

            d.addBoth(gotResult)

        if not unbuffered:
            d.addCallback(_BufferedResponse)

        return d.addCallback(_Response, cookies)

    def __init__(self, agent, cookiejar=None,
                 data_to_body_producer=IBodyProducer):
        self._agent = agent
        if cookiejar is None:
            cookiejar = CookieJar()
        self._cookiejar = cookiejar
        self._data_to_body_producer = data_to_body_producer

    def request(
        self,
        method,
        url,
        *,
        params=None,
        headers=None,
        data=None,
        files=None,
        json=_NOTHING,
        auth=None,
        cookies=None,
        allow_redirects=True,
        browser_like_redirects=False,
        unbuffered=False,
        reactor=None,
        timeout=None,
        _stacklevel=2,
    ):
        """
        See :func:`treq.request()`.
        """
        method = method.encode('ascii').upper()

        if isinstance(url, DecodedURL):
            parsed_url = url.encoded_url
        elif isinstance(url, EncodedURL):
            parsed_url = url
        elif isinstance(url, str):
            # We use hyperlink in lazy mode so that users can pass arbitrary
            # bytes in the path and querystring.
            parsed_url = EncodedURL.from_text(url)
        else:
            parsed_url = EncodedURL.from_text(url.decode('ascii'))

        # Join parameters provided in the URL
        # and the ones passed as argument.
        if params:
            parsed_url = parsed_url.replace(
                query=parsed_url.query + tuple(_coerced_query_params(params))
            )

        url = parsed_url.to_uri().to_text().encode('ascii')

        headers = self._request_headers(headers, _stacklevel + 1)

        bodyProducer, contentType = self._request_body(data, files, json,
                                                       stacklevel=_stacklevel + 1)
        if contentType is not None:
            headers.setRawHeaders(b'Content-Type', [contentType])

        if not isinstance(cookies, CookieJar):
            cookies = _scoped_cookiejar_from_dict(parsed_url, cookies)

        cookies = merge_cookies(self._cookiejar, cookies)
        wrapped_agent = CookieAgent(self._agent, cookies)

        if allow_redirects:
            if browser_like_redirects:
                wrapped_agent = BrowserLikeRedirectAgent(wrapped_agent)
            else:
                wrapped_agent = RedirectAgent(wrapped_agent)

        wrapped_agent = ContentDecoderAgent(wrapped_agent,
                                            [(b'gzip', GzipDecoder)])

        if auth:
            wrapped_agent = add_auth(wrapped_agent, auth)

        d = wrapped_agent.request(
            method, url, headers=headers,
            bodyProducer=bodyProducer)

        if reactor is None:
            from twisted.internet import reactor
        if timeout:
            delayedCall = reactor.callLater(timeout, d.cancel)

            def gotResult(result):
                if delayedCall.active():
                    delayedCall.cancel()
                return result

            d.addBoth(gotResult)

        if not unbuffered:
            d.addCallback(_BufferedResponse)

        return d.addCallback(_Response, cookies)

def _scoped_cookiejar_from_dict(url_object, cookie_dict):
    """
    Create a CookieJar from a dictionary whose cookies are all scoped to the
    given URL's origin.

    @note: This does not scope the cookies to any particular path, only the
        host, port, and scheme of the given URL.
    """
    cookie_jar = CookieJar()
    if cookie_dict is None:
        return cookie_jar
    for k, v in cookie_dict.items():
        secure = url_object.scheme == 'https'
        port_specified = not (
            (url_object.scheme == "https" and url_object.port == 443)
            or (url_object.scheme == "http" and url_object.port == 80)
        )
        port = str(url_object.port)
        domain = url_object.host
        netscape_domain = domain if '.' in domain else domain + '.local'

        cookie_jar.set_cookie(
            Cookie(
                # Scoping
                domain=netscape_domain,
                port=port,
                secure=secure,
                port_specified=port_specified,

                # Contents
                name=k,
                value=v,

                # Constant/always-the-same stuff
                version=0,
                path="/",
                expires=None,
                discard=False,
                comment=None,
                comment_url=None,
                rfc2109=False,
                path_specified=False,
                domain_specified=False,
                domain_initial_dot=False,
                rest=[],
            )
        )
    return cookie_jar

--- a/src/treq/client.py
+++ b/src/treq/client.py
@@ -1,5 +1,7 @@
     def __init__(self, agent, cookiejar=None,
                  data_to_body_producer=IBodyProducer):
         self._agent = agent
-        self._cookiejar = cookiejar or cookiejar_from_dict({})
+        if cookiejar is None:
+            cookiejar = CookieJar()
+        self._cookiejar = cookiejar
         self._data_to_body_producer = data_to_body_producer

--- a/src/treq/client.py
+++ b/src/treq/client.py
@@ -50,7 +50,7 @@
             headers.setRawHeaders(b'Content-Type', [contentType])

         if not isinstance(cookies, CookieJar):
-            cookies = cookiejar_from_dict(cookies)
+            cookies = _scoped_cookiejar_from_dict(parsed_url, cookies)

         cookies = merge_cookies(self._cookiejar, cookies)
         wrapped_agent = CookieAgent(self._agent, cookies)

--- /dev/null
+++ b/src/treq/client.py
@@ -0,0 +1,48 @@
+def _scoped_cookiejar_from_dict(url_object, cookie_dict):
+    """
+    Create a CookieJar from a dictionary whose cookies are all scoped to the
+    given URL's origin.
+
+    @note: This does not scope the cookies to any particular path, only the
+        host, port, and scheme of the given URL.
+    """
+    cookie_jar = CookieJar()
+    if cookie_dict is None:
+        return cookie_jar
+    for k, v in cookie_dict.items():
+        secure = url_object.scheme == 'https'
+        port_specified = not (
+            (url_object.scheme == "https" and url_object.port == 443)
+            or (url_object.scheme == "http" and url_object.port == 80)
+        )
+        port = str(url_object.port)
+        domain = url_object.host
+        netscape_domain = domain if '.' in domain else domain + '.local'
+
+        cookie_jar.set_cookie(
+            Cookie(
+                # Scoping
+                domain=netscape_domain,
+                port=port,
+                secure=secure,
+                port_specified=port_specified,
+
+                # Contents
+                name=k,
+                value=v,
+
+                # Constant/always-the-same stuff
+                version=0,
+                path="/",
+                expires=None,
+                discard=False,
+                comment=None,
+                comment_url=None,
+                rfc2109=False,
+                path_specified=False,
+                domain_specified=False,
+                domain_initial_dot=False,
+                rest=[],
+            )
+        )
+    return cookie_jar


urllib3 before 1.25.9 allows CRLF injection if the attacker controls the HTTP request method, as demonstrated by inserting CR and LF control characters in the first argument of putrequest(). NOTE: this is similar to CVE-2020-26116.

Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')

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

func (md *Metadata) Validate() error {
	if md == nil {
		return ValidationError("chart.metadata is required")
	}
	if md.APIVersion == "" {
		return ValidationError("chart.metadata.apiVersion is required")
	}
	if md.Name == "" {
		return ValidationError("chart.metadata.name is required")
	}
	if md.Version == "" {
		return ValidationError("chart.metadata.version is required")
	}
	if !isValidChartType(md.Type) {
		return ValidationError("chart.metadata.type must be application or library")
	}
	// TODO validate valid semver here?
	return nil
}

func (md *Metadata) Validate() error {
	if md == nil {
		return ValidationError("chart.metadata is required")
	}
	if md.APIVersion == "" {
		return ValidationError("chart.metadata.apiVersion is required")
	}
	if md.Name == "" {
		return ValidationError("chart.metadata.name is required")
	}
	if md.Version == "" {
		return ValidationError("chart.metadata.version is required")
	}
	if !isValidChartType(md.Type) {
		return ValidationError("chart.metadata.type must be application or library")
	}

	// Aliases need to be validated here to make sure that the alias name does
	// not contain any illegal characters.
	for _, dependency := range md.Dependencies {
		if err := validateDependency(dependency); err != nil {
			return err
		}
	}

	// TODO validate valid semver here?
	return nil
}

func ValidationErrorf(msg string, args ...interface{}) ValidationError {
	return ValidationError(fmt.Sprintf(msg, args...))
}

func validateDependency(dep *Dependency) error {
	if len(dep.Alias) > 0 && !aliasNameFormat.MatchString(dep.Alias) {
		return ValidationErrorf("dependency %q has disallowed characters in the alias", dep.Name)
	}
	return nil
}

--- a/pkg/chart/metadata.go
+++ b/pkg/chart/metadata.go
@@ -14,6 +14,15 @@
 	if !isValidChartType(md.Type) {
 		return ValidationError("chart.metadata.type must be application or library")
 	}
+
+	// Aliases need to be validated here to make sure that the alias name does
+	// not contain any illegal characters.
+	for _, dependency := range md.Dependencies {
+		if err := validateDependency(dependency); err != nil {
+			return err
+		}
+	}
+
 	// TODO validate valid semver here?
 	return nil
 }

--- /dev/null
+++ b/pkg/chart/metadata.go
@@ -0,0 +1 @@
+var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$")

--- /dev/null
+++ b/pkg/chart/metadata.go
@@ -0,0 +1,3 @@
+func ValidationErrorf(msg string, args ...interface{}) ValidationError {
+	return ValidationError(fmt.Sprintf(msg, args...))
+}

--- /dev/null
+++ b/pkg/chart/metadata.go
@@ -0,0 +1,6 @@
+func validateDependency(dep *Dependency) error {
+	if len(dep.Alias) > 0 && !aliasNameFormat.MatchString(dep.Alias) {
+		return ValidationErrorf("dependency %q has disallowed characters in the alias", dep.Name)
+	}
+	return nil
+}


In systeminformation (npm package) before version 4.31.1 there is a command injection vulnerability. The problem was fixed in version 4.31.1 with a shell string sanitation fix.


The ip package before 1.1.9 for Node.js might allow SSRF because some IP addresses (such as 0x7f.1) are improperly categorized as globally routable via isPublic.

Server-Side Request Forgery (SSRF)

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

ip.isPrivate = function (addr) {
  // check loopback addresses first
  if (ip.isLoopback(addr)) {
    return true;
  }

  // ensure the ipv4 address is valid
  if (!ip.isV6Format(addr)) {
    const ipl = ip.normalizeToLong(addr);
    if (ipl < 0) {
      throw new Error('invalid ipv4 address');
    }
    // normalize the address for the private range checks that follow
    addr = ip.fromLong(ipl);
  }

  // check private ranges
  return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
    || /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
    || /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i
      .test(addr)
    || /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
    || /^f[cd][0-9a-f]{2}:/i.test(addr)
    || /^fe80:/i.test(addr)
    || /^::1$/.test(addr)
    || /^::$/.test(addr);
};

ip.isLoopback = function (addr) {
  // If addr is an IPv4 address in long integer form (no dots and no colons), convert it
  if (!/\./.test(addr) && !/:/.test(addr)) {
    addr = ip.fromLong(Number(addr));
  }

  return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
    .test(addr)
    || /^0177\./.test(addr)
    || /^0x7f\./i.test(addr)
    || /^fe80::1$/i.test(addr)
    || /^::1$/.test(addr)
    || /^::$/.test(addr);
};

ip.normalizeToLong = function (addr) {
  const parts = addr.split('.').map(part => {
    // Handle hexadecimal format
    if (part.startsWith('0x') || part.startsWith('0X')) {
      return parseInt(part, 16);
    }
    // Handle octal format (strictly digits 0-7 after a leading zero)
    else if (part.startsWith('0') && part !== '0' && /^[0-7]+$/.test(part)) {
      return parseInt(part, 8);
    }
    // Handle decimal format, reject invalid leading zeros
    else if (/^[1-9]\d*$/.test(part) || part === '0') {
      return parseInt(part, 10);
    }
    // Return NaN for invalid formats to indicate parsing failure
    else {
      return NaN;
    }
  });

  if (parts.some(isNaN)) return -1; // Indicate error with -1

  let val = 0;
  const n = parts.length;

  switch (n) {
  case 1:
    val = parts[0];
    break;
  case 2:
    if (parts[0] > 0xff || parts[1] > 0xffffff) return -1;
    val = (parts[0] << 24) | (parts[1] & 0xffffff);
    break;
  case 3:
    if (parts[0] > 0xff || parts[1] > 0xff || parts[2] > 0xffff) return -1;
    val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] & 0xffff);
    break;
  case 4:
    if (parts.some(part => part > 0xff)) return -1;
    val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
    break;
  default:
    return -1; // Error case
  }

  return val >>> 0;
};

--- a/lib/ip.js
+++ b/lib/ip.js
@@ -1,10 +1,24 @@
 ip.isPrivate = function (addr) {
-  return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i
-    .test(addr)
+  // check loopback addresses first
+  if (ip.isLoopback(addr)) {
+    return true;
+  }
+
+  // ensure the ipv4 address is valid
+  if (!ip.isV6Format(addr)) {
+    const ipl = ip.normalizeToLong(addr);
+    if (ipl < 0) {
+      throw new Error('invalid ipv4 address');
+    }
+    // normalize the address for the private range checks that follow
+    addr = ip.fromLong(ipl);
+  }
+
+  // check private ranges
+  return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
     || /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
     || /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i
       .test(addr)
-    || /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
     || /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
     || /^f[cd][0-9a-f]{2}:/i.test(addr)
     || /^fe80:/i.test(addr)

--- a/lib/ip.js
+++ b/lib/ip.js
@@ -1,7 +1,14 @@
 ip.isLoopback = function (addr) {
+  // If addr is an IPv4 address in long integer form (no dots and no colons), convert it
+  if (!/\./.test(addr) && !/:/.test(addr)) {
+    addr = ip.fromLong(Number(addr));
+  }
+
   return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
     .test(addr)
-    || /^fe80::1$/.test(addr)
+    || /^0177\./.test(addr)
+    || /^0x7f\./i.test(addr)
+    || /^fe80::1$/i.test(addr)
     || /^::1$/.test(addr)
     || /^::$/.test(addr);
 };

--- /dev/null
+++ b/lib/ip.js
@@ -0,0 +1,47 @@
+ip.normalizeToLong = function (addr) {
+  const parts = addr.split('.').map(part => {
+    // Handle hexadecimal format
+    if (part.startsWith('0x') || part.startsWith('0X')) {
+      return parseInt(part, 16);
+    }
+    // Handle octal format (strictly digits 0-7 after a leading zero)
+    else if (part.startsWith('0') && part !== '0' && /^[0-7]+$/.test(part)) {
+      return parseInt(part, 8);
+    }
+    // Handle decimal format, reject invalid leading zeros
+    else if (/^[1-9]\d*$/.test(part) || part === '0') {
+      return parseInt(part, 10);
+    }
+    // Return NaN for invalid formats to indicate parsing failure
+    else {
+      return NaN;
+    }
+  });
+
+  if (parts.some(isNaN)) return -1; // Indicate error with -1
+
+  let val = 0;
+  const n = parts.length;
+
+  switch (n) {
+  case 1:
+    val = parts[0];
+    break;
+  case 2:
+    if (parts[0] > 0xff || parts[1] > 0xffffff) return -1;
+    val = (parts[0] << 24) | (parts[1] & 0xffffff);
+    break;
+  case 3:
+    if (parts[0] > 0xff || parts[1] > 0xff || parts[2] > 0xffff) return -1;
+    val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] & 0xffff);
+    break;
+  case 4:
+    if (parts.some(part => part > 0xff)) return -1;
+    val = (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
+    break;
+  default:
+    return -1; // Error case
+  }
+
+  return val >>> 0;
+};


core/api/user.go in Harbor 1.7.0 through 1.8.2 allows non-admin users to create admin accounts via the POST /api/users API, when Harbor is setup with DB as authentication backend and allow user to do self-registration. Fixed version: v1.7.6 v1.8.3. v.1.9.0. Workaround without applying the fix: configure Harbor to use non-DB authentication backend such as LDAP.

Missing Authorization

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

Authorization Bypass Through User-Controlled Key

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

func (ua *UserAPI) Post() {

	if !(ua.AuthMode == common.DBAuth) {
		ua.SendForbiddenError(errors.New(""))
		return
	}

	if !(ua.SelfRegistration || ua.IsAdmin) {
		log.Warning("Registration can only be used by admin role user when self-registration is off.")
		ua.SendForbiddenError(errors.New(""))
		return
	}

	user := models.User{}
	if err := ua.DecodeJSONReq(&user); err != nil {
		ua.SendBadRequestError(err)
		return
	}
	err := validate(user)
	if err != nil {
		log.Warningf("Bad request in Register: %v", err)
		ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
		return
	}
	userExist, err := dao.UserExists(user, "username")
	if err != nil {
		log.Errorf("Error occurred in Register: %v", err)
		ua.SendInternalServerError(errors.New("internal error"))
		return
	}
	if userExist {
		log.Warning("username has already been used!")
		ua.SendConflictError(errors.New("username has already been used"))
		return
	}
	emailExist, err := dao.UserExists(user, "email")
	if err != nil {
		log.Errorf("Error occurred in change user profile: %v", err)
		ua.SendInternalServerError(errors.New("internal error"))
		return
	}
	if emailExist {
		log.Warning("email has already been used!")
		ua.SendConflictError(errors.New("email has already been used"))
		return
	}
	userID, err := dao.Register(user)
	if err != nil {
		log.Errorf("Error occurred in Register: %v", err)
		ua.SendInternalServerError(errors.New("internal error"))
		return
	}

	ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
}

func (ua *UserAPI) Post() {

	if !(ua.AuthMode == common.DBAuth) {
		ua.SendForbiddenError(errors.New(""))
		return
	}

	if !(ua.SelfRegistration || ua.IsAdmin) {
		log.Warning("Registration can only be used by admin role user when self-registration is off.")
		ua.SendForbiddenError(errors.New(""))
		return
	}

	user := models.User{}
	if err := ua.DecodeJSONReq(&user); err != nil {
		ua.SendBadRequestError(err)
		return
	}
	err := validate(user)
	if err != nil {
		log.Warningf("Bad request in Register: %v", err)
		ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
		return
	}

	if !ua.IsAdmin && user.HasAdminRole {
		msg := "Non-admin cannot create an admin user."
		log.Errorf(msg)
		ua.SendForbiddenError(errors.New(msg))
		return
	}

	userExist, err := dao.UserExists(user, "username")
	if err != nil {
		log.Errorf("Error occurred in Register: %v", err)
		ua.SendInternalServerError(errors.New("internal error"))
		return
	}
	if userExist {
		log.Warning("username has already been used!")
		ua.SendConflictError(errors.New("username has already been used"))
		return
	}
	emailExist, err := dao.UserExists(user, "email")
	if err != nil {
		log.Errorf("Error occurred in change user profile: %v", err)
		ua.SendInternalServerError(errors.New("internal error"))
		return
	}
	if emailExist {
		log.Warning("email has already been used!")
		ua.SendConflictError(errors.New("email has already been used"))
		return
	}

	userID, err := dao.Register(user)
	if err != nil {
		log.Errorf("Error occurred in Register: %v", err)
		ua.SendInternalServerError(errors.New("internal error"))
		return
	}

	ua.Redirect(http.StatusCreated, strconv.FormatInt(userID, 10))
}

--- a/src/core/api/user.go
+++ b/src/core/api/user.go
@@ -22,6 +22,14 @@
 		ua.RenderError(http.StatusBadRequest, "register error:"+err.Error())
 		return
 	}
+
+	if !ua.IsAdmin && user.HasAdminRole {
+		msg := "Non-admin cannot create an admin user."
+		log.Errorf(msg)
+		ua.SendForbiddenError(errors.New(msg))
+		return
+	}
+
 	userExist, err := dao.UserExists(user, "username")
 	if err != nil {
 		log.Errorf("Error occurred in Register: %v", err)
@@ -44,6 +52,7 @@
 		ua.SendConflictError(errors.New("email has already been used"))
 		return
 	}
+
 	userID, err := dao.Register(user)
 	if err != nil {
 		log.Errorf("Error occurred in Register: %v", err)


gomatrixserverlib is a Go library for matrix protocol federation. Dendrite is a Matrix homeserver written in Go, an alternative to Synapse. The power level parsing within gomatrixserverlib was failing to parse the `"events_default"` key of the `m.room.power_levels` event, defaulting the event default power level to zero in all cases. Power levels are the matrix terminology for user access level. In rooms where the `"events_default"` power level had been changed, this could result in events either being incorrectly authorised or rejected by Dendrite servers. gomatrixserverlib contains a fix as of commit `723fd49` and Dendrite 0.9.3 has been updated accordingly. Matrix rooms where the `"events_default"` power level has not been changed from the default of zero are not vulnerable. Users are advised to upgrade. There are no known workarounds for this issue.

Incorrect Authorization

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

func NewPowerLevelContentFromEvent(event *Event) (c PowerLevelContent, err error) {
	// Set the levels to their default values.
	c.Defaults()

	var strict bool
	if strict, err = event.roomVersion.RequireIntegerPowerLevels(); err != nil {
		return
	} else if strict {
		// Unmarshal directly to PowerLevelContent, since that will kick up an
		// error if one of the power levels isn't an int64.
		if err = json.Unmarshal(event.Content(), &c); err != nil {
			err = errorf("unparsable power_levels event content: %s", err.Error())
			return
		}
	} else {
		// We can't extract the JSON directly to the powerLevelContent because we
		// need to convert string values to int values.
		var content struct {
			InviteLevel        levelJSONValue            `json:"invite"`
			BanLevel           levelJSONValue            `json:"ban"`
			KickLevel          levelJSONValue            `json:"kick"`
			RedactLevel        levelJSONValue            `json:"redact"`
			UserLevels         map[string]levelJSONValue `json:"users"`
			UsersDefaultLevel  levelJSONValue            `json:"users_default"`
			EventLevels        map[string]levelJSONValue `json:"events"`
			StateDefaultLevel  levelJSONValue            `json:"state_default"`
			EventDefaultLevel  levelJSONValue            `json:"event_default"`
			NotificationLevels map[string]levelJSONValue `json:"notifications"`
		}
		if err = json.Unmarshal(event.Content(), &content); err != nil {
			err = errorf("unparsable power_levels event content: %s", err.Error())
			return
		}

		// Update the levels with the values that are present in the event content.
		content.InviteLevel.assignIfExists(&c.Invite)
		content.BanLevel.assignIfExists(&c.Ban)
		content.KickLevel.assignIfExists(&c.Kick)
		content.RedactLevel.assignIfExists(&c.Redact)
		content.UsersDefaultLevel.assignIfExists(&c.UsersDefault)
		content.StateDefaultLevel.assignIfExists(&c.StateDefault)
		content.EventDefaultLevel.assignIfExists(&c.EventsDefault)

		for k, v := range content.UserLevels {
			if c.Users == nil {
				c.Users = make(map[string]int64)
			}
			c.Users[k] = v.value
		}

		for k, v := range content.EventLevels {
			if c.Events == nil {
				c.Events = make(map[string]int64)
			}
			c.Events[k] = v.value
		}

		for k, v := range content.NotificationLevels {
			if c.Notifications == nil {
				c.Notifications = make(map[string]int64)
			}
			c.Notifications[k] = v.value
		}
	}

	return
}

func NewPowerLevelContentFromEvent(event *Event) (c PowerLevelContent, err error) {
	// Set the levels to their default values.
	c.Defaults()

	var strict bool
	if strict, err = event.roomVersion.RequireIntegerPowerLevels(); err != nil {
		return
	} else if strict {
		// Unmarshal directly to PowerLevelContent, since that will kick up an
		// error if one of the power levels isn't an int64.
		if err = json.Unmarshal(event.Content(), &c); err != nil {
			err = errorf("unparsable power_levels event content: %s", err.Error())
			return
		}
	} else {
		// We can't extract the JSON directly to the powerLevelContent because we
		// need to convert string values to int values.
		var content struct {
			InviteLevel        levelJSONValue            `json:"invite"`
			BanLevel           levelJSONValue            `json:"ban"`
			KickLevel          levelJSONValue            `json:"kick"`
			RedactLevel        levelJSONValue            `json:"redact"`
			UserLevels         map[string]levelJSONValue `json:"users"`
			UsersDefaultLevel  levelJSONValue            `json:"users_default"`
			EventLevels        map[string]levelJSONValue `json:"events"`
			StateDefaultLevel  levelJSONValue            `json:"state_default"`
			EventDefaultLevel  levelJSONValue            `json:"events_default"`
			NotificationLevels map[string]levelJSONValue `json:"notifications"`
		}
		if err = json.Unmarshal(event.Content(), &content); err != nil {
			err = errorf("unparsable power_levels event content: %s", err.Error())
			return
		}

		// Update the levels with the values that are present in the event content.
		content.InviteLevel.assignIfExists(&c.Invite)
		content.BanLevel.assignIfExists(&c.Ban)
		content.KickLevel.assignIfExists(&c.Kick)
		content.RedactLevel.assignIfExists(&c.Redact)
		content.UsersDefaultLevel.assignIfExists(&c.UsersDefault)
		content.StateDefaultLevel.assignIfExists(&c.StateDefault)
		content.EventDefaultLevel.assignIfExists(&c.EventsDefault)

		for k, v := range content.UserLevels {
			if c.Users == nil {
				c.Users = make(map[string]int64)
			}
			c.Users[k] = v.value
		}

		for k, v := range content.EventLevels {
			if c.Events == nil {
				c.Events = make(map[string]int64)
			}
			c.Events[k] = v.value
		}

		for k, v := range content.NotificationLevels {
			if c.Notifications == nil {
				c.Notifications = make(map[string]int64)
			}
			c.Notifications[k] = v.value
		}
	}

	return
}

--- a/eventcontent.go
+++ b/eventcontent.go
@@ -24,7 +24,7 @@
 			UsersDefaultLevel  levelJSONValue            `json:"users_default"`
 			EventLevels        map[string]levelJSONValue `json:"events"`
 			StateDefaultLevel  levelJSONValue            `json:"state_default"`
-			EventDefaultLevel  levelJSONValue            `json:"event_default"`
+			EventDefaultLevel  levelJSONValue            `json:"events_default"`
 			NotificationLevels map[string]levelJSONValue `json:"notifications"`
 		}
 		if err = json.Unmarshal(event.Content(), &content); err != nil {

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

cd /workspace/gomatrixserverlib
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestNegativePowerLevels$ github.com/matrix-org/gomatrixserverlib

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

cd /workspace/gomatrixserverlib
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestAuthEvents|TestStateNeededForInvite|TestStateNeededForCreate|TestAllowedNoFederation|TestAllowedWithPowerLevels|TestStateNeededForMessage|TestDemoteUserDefaultPowerLevelBelowOwn|TestAllowedInviteFrom3PID|TestPromoteUserDefaultLevelAboveOwn|TestAllowedEmptyRoom|TestRedactAllowed|TestStateNeededForInvite3PID|TestStateNeededForAlias|TestAllowedFirstJoin|TestStateNeededForJoin|TestAllowedWithNoPowerLevels)$' github.com/matrix-org/gomatrixserverlib


OctoRPKI does not escape a URI with a filename containing "..", this allows a repository to create a file, (ex. rsync://example.org/repo/../../etc/cron.daily/evil.roa), which would then be written to disk outside the base cache folder. This could allow for remote code execution on the host machine OctoRPKI is running on.

External Control of File Name or Path

The product allows user input to control or influence paths or file names that are used in filesystem operations.

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

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

func (s *state) WriteRsyncFileOnDisk(path string, data []byte, withdraw bool) error {
	fPath, err := syncpki.GetDownloadPath(path, true)
	if err != nil {
		log.Fatal(err)
	}
	err = os.MkdirAll(filepath.Join(s.Basepath, fPath), os.ModePerm)
	if err != nil {
		log.Fatal(err)
	}
	fPath, err = syncpki.GetDownloadPath(path, false)
	if err != nil {
		log.Fatal(err)
	}
	f, err := os.Create(filepath.Join(s.Basepath, fPath))
	if err != nil {
		return err
	}
	f.Write(data)
	f.Close()
	return nil
}

func ExtractPathManifest(mft *librpki.RPKIManifest) []*PKIFile {
	fileList := make([]*PKIFile, 0)
	for _, file := range mft.Content.FileList {
		curFile := file.Name
		path := string(curFile)
		item := PKIFile{
			Type:         DetermineType(path),
			Path:         path,
			ManifestHash: file.GetHash(),
		}
		fileList = append(fileList, &item)
	}
	return fileList
}

func (s *state) WriteRsyncFileOnDisk(path string, data []byte, withdraw bool) error {
	fPath, err := syncpki.GetDownloadPath(path, true)
	if err != nil {
		log.Fatal(err)
	}
	err = os.MkdirAll(filepath.Join(s.Basepath, fPath), os.ModePerm)
	if err != nil {
		log.Fatal(err)
	}
	fPath, err = syncpki.GetDownloadPath(path, false)
	if err != nil {
		log.Fatal(err)
	}
	// GHSA-cqh2-vc2f-q4fh: Prevent parent directory writes outside of Basepath
	fPath = strings.ReplaceAll(fPath, "../", "")

	f, err := os.Create(filepath.Join(s.Basepath, fPath))
	if err != nil {
		return err
	}
	f.Write(data)
	f.Close()
	return nil
}

func ExtractPathManifest(mft *librpki.RPKIManifest) []*PKIFile {
	fileList := make([]*PKIFile, 0)
	for _, file := range mft.Content.FileList {
		curFile := file.Name
		path := string(curFile)
		// GHSA-cqh2-vc2f-q4fh: Prevent file path references to parent
		// directories.
		path = strings.ReplaceAll(path, "../", "")
		item := PKIFile{
			Type:         DetermineType(path),
			Path:         path,
			ManifestHash: file.GetHash(),
		}
		fileList = append(fileList, &item)
	}
	return fileList
}

--- a/cmd/octorpki/octorpki.go
+++ b/cmd/octorpki/octorpki.go
@@ -11,6 +11,9 @@
 	if err != nil {
 		log.Fatal(err)
 	}
+	// GHSA-cqh2-vc2f-q4fh: Prevent parent directory writes outside of Basepath
+	fPath = strings.ReplaceAll(fPath, "../", "")
+
 	f, err := os.Create(filepath.Join(s.Basepath, fPath))
 	if err != nil {
 		return err

--- a/validator/pki/pki.go
+++ b/validator/pki/pki.go
@@ -3,6 +3,9 @@
 	for _, file := range mft.Content.FileList {
 		curFile := file.Name
 		path := string(curFile)
+		// GHSA-cqh2-vc2f-q4fh: Prevent file path references to parent
+		// directories.
+		path = strings.ReplaceAll(path, "../", "")
 		item := PKIFile{
 			Type:         DetermineType(path),
 			Path:         path,


Path Traversal: '\..\filename' in GitHub repository mlflow/mlflow prior to 2.9.2.

def validate_path_is_safe(path):
    """
    Validates that the specified path is safe to join with a trusted prefix. This is a security
    measure to prevent path traversal attacks.
    A valid path should:
        not contain separators other than '/'
        not contain .. to navigate to parent dir in path
        not be an absolute path
    """
    from mlflow.utils.file_utils import local_file_uri_to_path

    exc = MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)
    if any((s in path) for s in ("#", "%23")):
        raise exc

    if is_file_uri(path):
        path = local_file_uri_to_path(path)
    if (
        any((s in path) for s in _OS_ALT_SEPS)
        or ".." in path.split("/")
        or pathlib.PureWindowsPath(path).is_absolute()
        or pathlib.PurePosixPath(path).is_absolute()
        or (is_windows() and len(path) >= 2 and path[1] == ":")
    ):
        raise exc

def validate_path_is_safe(path):
    """
    Validates that the specified path is safe to join with a trusted prefix. This is a security
    measure to prevent path traversal attacks.
    A valid path should:
        not contain separators other than '/'
        not contain .. to navigate to parent dir in path
        not be an absolute path
    """
    from mlflow.utils.file_utils import local_file_uri_to_path

    # We must decode URL before validating it
    path = urllib.parse.unquote(path)

    exc = MlflowException("Invalid path", error_code=INVALID_PARAMETER_VALUE)
    if any((s in path) for s in ("#", "%23")):
        raise exc

    if is_file_uri(path):
        path = local_file_uri_to_path(path)
    if (
        any((s in path) for s in _OS_ALT_SEPS)
        or ".." in path.split("/")
        or pathlib.PureWindowsPath(path).is_absolute()
        or pathlib.PurePosixPath(path).is_absolute()
        or (is_windows() and len(path) >= 2 and path[1] == ":")
    ):
        raise exc

--- a/mlflow/utils/uri.py
+++ b/mlflow/utils/uri.py
@@ -9,7 +9,10 @@
     """
     from mlflow.utils.file_utils import local_file_uri_to_path

-    exc = MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)
+    # We must decode URL before validating it
+    path = urllib.parse.unquote(path)
+
+    exc = MlflowException("Invalid path", error_code=INVALID_PARAMETER_VALUE)
     if any((s in path) for s in ("#", "%23")):
         raise exc

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

cd /workspace/mlflow
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2023-6831/bin/python -m pytest tests/server/test_handlers.py::test_delete_artifact_mlflow_artifacts_throws_for_malicious_path tests/store/artifact/test_http_artifact_repo.py::test_list_artifacts_malicious_path tests/utils/test_uri.py::test_validate_path_is_safe_bad -p no:warning --disable-warnings

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

cd /workspace/mlflow
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2023-6831/bin/python -m pytest tests/server/test_handlers.py tests/store/artifact/test_http_artifact_repo.py tests/utils/test_uri.py -k "not test_mlflow_server_with_installed_plugin and not test_list_artifacts_malicious_path" -p no:warning --disable-warnings


In Waitress through version 1.4.0, if a proxy server is used in front of waitress, an invalid request may be sent by an attacker that bypasses the front-end and is parsed differently by waitress leading to a potential for HTTP request smuggling. Specially crafted requests containing special whitespace characters in the Transfer-Encoding header would get parsed by Waitress as being a chunked request, but a front-end server would use the Content-Length instead as the Transfer-Encoding header is considered invalid due to containing invalid characters. If a front-end server does HTTP pipelining to a backend Waitress server this could lead to HTTP request splitting which may lead to potential cache poisoning or unexpected information disclosure. This issue is fixed in Waitress 1.4.1 through more strict HTTP field validation.

Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

The product acts as an intermediary HTTP agent
         (such as a proxy or firewall) in the data flow between two
         entities such as a client and server, but it does not
         interpret malformed HTTP requests or responses in ways that
         are consistent with how the messages will be processed by
         those entities that are at the ultimate destination.

    def parse_header(self, header_plus):
        """
        Parses the header_plus block of text (the headers plus the
        first line of the request).
        """
        index = header_plus.find(b"\r\n")
        if index >= 0:
            first_line = header_plus[:index].rstrip()
            header = header_plus[index + 2 :]
        else:
            raise ParsingError("HTTP message header invalid")

        if b"\r" in first_line or b"\n" in first_line:
            raise ParsingError("Bare CR or LF found in HTTP message")

        self.first_line = first_line  # for testing

        lines = get_header_lines(header)

        headers = self.headers
        for line in lines:
            index = line.find(b":")
            if index > 0:
                key = line[:index]

                if key != key.strip():
                    raise ParsingError("Invalid whitespace after field-name")

                if b"_" in key:
                    continue
                value = line[index + 1 :].strip()
                key1 = tostr(key.upper().replace(b"-", b"_"))
                # If a header already exists, we append subsequent values
                # seperated by a comma. Applications already need to handle
                # the comma seperated values, as HTTP front ends might do
                # the concatenation for you (behavior specified in RFC2616).
                try:
                    headers[key1] += tostr(b", " + value)
                except KeyError:
                    headers[key1] = tostr(value)
            # else there's garbage in the headers?

        # command, uri, version will be bytes
        command, uri, version = crack_first_line(first_line)
        version = tostr(version)
        command = tostr(command)
        self.command = command
        self.version = version
        (
            self.proxy_scheme,
            self.proxy_netloc,
            self.path,
            self.query,
            self.fragment,
        ) = split_uri(uri)
        self.url_scheme = self.adj.url_scheme
        connection = headers.get("CONNECTION", "")

        if version == "1.0":
            if connection.lower() != "keep-alive":
                self.connection_close = True

        if version == "1.1":
            # since the server buffers data from chunked transfers and clients
            # never need to deal with chunked requests, downstream clients
            # should not see the HTTP_TRANSFER_ENCODING header; we pop it
            # here
            te = headers.pop("TRANSFER_ENCODING", "")

            encodings = [encoding.strip().lower() for encoding in te.split(",") if encoding]

            for encoding in encodings:
                # Out of the transfer-codings listed in
                # https://tools.ietf.org/html/rfc7230#section-4 we only support
                # chunked at this time.

                # Note: the identity transfer-coding was removed in RFC7230:
                # https://tools.ietf.org/html/rfc7230#appendix-A.2 and is thus
                # not supported
                if encoding not in {"chunked"}:
                    raise TransferEncodingNotImplemented(
                        "Transfer-Encoding requested is not supported."
                    )

            if encodings and encodings[-1] == "chunked":
                self.chunked = True
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = ChunkedReceiver(buf)
            elif encodings:  # pragma: nocover
                raise TransferEncodingNotImplemented(
                    "Transfer-Encoding requested is not supported."
                )

            expect = headers.get("EXPECT", "").lower()
            self.expect_continue = expect == "100-continue"
            if connection.lower() == "close":
                self.connection_close = True

        if not self.chunked:
            try:
                cl = int(headers.get("CONTENT_LENGTH", 0))
            except ValueError:
                raise ParsingError("Content-Length is invalid")

            self.content_length = cl
            if cl > 0:
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = FixedStreamReceiver(cl, buf)

def get_header_lines(header):
    """
    Splits the header into lines, putting multi-line headers together.
    """
    r = []
    lines = header.split(b"\r\n")
    for line in lines:
        if b"\r" in line or b"\n" in line:
            raise ParsingError('Bare CR or LF found in header line "%s"' % tostr(line))

        if line.startswith((b" ", b"\t")):
            if not r:
                # https://corte.si/posts/code/pathod/pythonservers/index.html
                raise ParsingError('Malformed header line "%s"' % tostr(line))
            r[-1] += line
        else:
            r.append(line)
    return r

vchar_re = "\x21-\x7e"

# RFC 7230 Section 3.2.6 "Field Value Components":
# quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
# qdtext        = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
# obs-text      = %x80-FF
# quoted-pair   = "\" ( HTAB / SP / VCHAR / obs-text )
obs_text_re = "\x80-\xff"

    def parse_header(self, header_plus):
        """
        Parses the header_plus block of text (the headers plus the
        first line of the request).
        """
        index = header_plus.find(b"\r\n")
        if index >= 0:
            first_line = header_plus[:index].rstrip()
            header = header_plus[index + 2 :]
        else:
            raise ParsingError("HTTP message header invalid")

        if b"\r" in first_line or b"\n" in first_line:
            raise ParsingError("Bare CR or LF found in HTTP message")

        self.first_line = first_line  # for testing

        lines = get_header_lines(header)

        headers = self.headers
        for line in lines:
            header = HEADER_FIELD.match(line)

            if not header:
                raise ParsingError("Invalid header")

            key, value = header.group('name', 'value')

            if b"_" in key:
                # TODO(xistence): Should we drop this request instead?
                continue

            value = value.strip()
            key1 = tostr(key.upper().replace(b"-", b"_"))
            # If a header already exists, we append subsequent values
            # seperated by a comma. Applications already need to handle
            # the comma seperated values, as HTTP front ends might do
            # the concatenation for you (behavior specified in RFC2616).
            try:
                headers[key1] += tostr(b", " + value)
            except KeyError:
                headers[key1] = tostr(value)

        # command, uri, version will be bytes
        command, uri, version = crack_first_line(first_line)
        version = tostr(version)
        command = tostr(command)
        self.command = command
        self.version = version
        (
            self.proxy_scheme,
            self.proxy_netloc,
            self.path,
            self.query,
            self.fragment,
        ) = split_uri(uri)
        self.url_scheme = self.adj.url_scheme
        connection = headers.get("CONNECTION", "")

        if version == "1.0":
            if connection.lower() != "keep-alive":
                self.connection_close = True

        if version == "1.1":
            # since the server buffers data from chunked transfers and clients
            # never need to deal with chunked requests, downstream clients
            # should not see the HTTP_TRANSFER_ENCODING header; we pop it
            # here
            te = headers.pop("TRANSFER_ENCODING", "")

            encodings = [encoding.strip().lower() for encoding in te.split(",") if encoding]

            for encoding in encodings:
                # Out of the transfer-codings listed in
                # https://tools.ietf.org/html/rfc7230#section-4 we only support
                # chunked at this time.

                # Note: the identity transfer-coding was removed in RFC7230:
                # https://tools.ietf.org/html/rfc7230#appendix-A.2 and is thus
                # not supported
                if encoding not in {"chunked"}:
                    raise TransferEncodingNotImplemented(
                        "Transfer-Encoding requested is not supported."
                    )

            if encodings and encodings[-1] == "chunked":
                self.chunked = True
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = ChunkedReceiver(buf)
            elif encodings:  # pragma: nocover
                raise TransferEncodingNotImplemented(
                    "Transfer-Encoding requested is not supported."
                )

            expect = headers.get("EXPECT", "").lower()
            self.expect_continue = expect == "100-continue"
            if connection.lower() == "close":
                self.connection_close = True

        if not self.chunked:
            try:
                cl = int(headers.get("CONTENT_LENGTH", 0))
            except ValueError:
                raise ParsingError("Content-Length is invalid")

            self.content_length = cl
            if cl > 0:
                buf = OverflowableBuffer(self.adj.inbuf_overflow)
                self.body_rcv = FixedStreamReceiver(cl, buf)

def get_header_lines(header):
    """
    Splits the header into lines, putting multi-line headers together.
    """
    r = []
    lines = header.split(b"\r\n")
    for line in lines:
        if not line:
            continue

        if b"\r" in line or b"\n" in line:
            raise ParsingError('Bare CR or LF found in header line "%s"' % tostr(line))

        if line.startswith((b" ", b"\t")):
            if not r:
                # https://corte.si/posts/code/pathod/pythonservers/index.html
                raise ParsingError('Malformed header line "%s"' % tostr(line))
            r[-1] += line
        else:
            r.append(line)
    return r

first_line_re = re.compile(
    b"([^ ]+) "
    b"((?:[^ :?#]+://[^ ?#/]*(?:[0-9]{1,5})?)?[^ ]+)"
    b"(( HTTP/([0-9.]+))$|$)"
)

vchar_re = VCHAR

# RFC 7230 Section 3.2.6 "Field Value Components":
# quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
# qdtext        = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
# obs-text      = %x80-FF
# quoted-pair   = "\" ( HTAB / SP / VCHAR / obs-text )
obs_text_re = OBS_TEXT

"""
This contains a bunch of RFC7230 definitions and regular expressions that are
needed to properly parse HTTP messages.
"""

import re

from .compat import tobytes

WS = "[ \t]"
OWS = WS + "{0,}?"
RWS = WS + "{1,}?"
BWS = OWS

# RFC 7230 Section 3.2.6 "Field Value Components":
# tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
#                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
#                / DIGIT / ALPHA
# obs-text      = %x80-FF
TCHAR = r"[!#$%&'*+\-.^_`|~0-9A-Za-z]"
OBS_TEXT = r"\x80-\xff"

TOKEN = TCHAR + "{1,}"

# RFC 5234 Appendix B.1 "Core Rules":
# VCHAR         =  %x21-7E
#                  ; visible (printing) characters
VCHAR = r"\x21-\x7e"

# header-field   = field-name ":" OWS field-value OWS
# field-name     = token
# field-value    = *( field-content / obs-fold )
# field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
# field-vchar    = VCHAR / obs-text

FIELD_VCHAR = "[" + VCHAR + OBS_TEXT + "]"
FIELD_CONTENT = FIELD_VCHAR + "(" + RWS + FIELD_VCHAR + "){0,}"
FIELD_VALUE = "(" + FIELD_CONTENT + "){0,}"

HEADER_FIELD = re.compile(
    tobytes(
        "^(?P<name>" + TOKEN + "):" + OWS + "(?P<value>" + FIELD_VALUE + ")" + OWS + "$"
    )
)

--- a/waitress/parser.py
+++ b/waitress/parser.py
@@ -19,26 +19,27 @@

         headers = self.headers
         for line in lines:
-            index = line.find(b":")
-            if index > 0:
-                key = line[:index]
+            header = HEADER_FIELD.match(line)

-                if key != key.strip():
-                    raise ParsingError("Invalid whitespace after field-name")
+            if not header:
+                raise ParsingError("Invalid header")

-                if b"_" in key:
-                    continue
-                value = line[index + 1 :].strip()
-                key1 = tostr(key.upper().replace(b"-", b"_"))
-                # If a header already exists, we append subsequent values
-                # seperated by a comma. Applications already need to handle
-                # the comma seperated values, as HTTP front ends might do
-                # the concatenation for you (behavior specified in RFC2616).
-                try:
-                    headers[key1] += tostr(b", " + value)
-                except KeyError:
-                    headers[key1] = tostr(value)
-            # else there's garbage in the headers?
+            key, value = header.group('name', 'value')
+
+            if b"_" in key:
+                # TODO(xistence): Should we drop this request instead?
+                continue
+
+            value = value.strip()
+            key1 = tostr(key.upper().replace(b"-", b"_"))
+            # If a header already exists, we append subsequent values
+            # seperated by a comma. Applications already need to handle
+            # the comma seperated values, as HTTP front ends might do
+            # the concatenation for you (behavior specified in RFC2616).
+            try:
+                headers[key1] += tostr(b", " + value)
+            except KeyError:
+                headers[key1] = tostr(value)

         # command, uri, version will be bytes
         command, uri, version = crack_first_line(first_line)

--- a/waitress/parser.py
+++ b/waitress/parser.py
@@ -5,6 +5,9 @@
     r = []
     lines = header.split(b"\r\n")
     for line in lines:
+        if not line:
+            continue
+
         if b"\r" in line or b"\n" in line:
             raise ParsingError('Bare CR or LF found in header line "%s"' % tostr(line))

@@ -16,3 +19,10 @@
         else:
             r.append(line)
     return r
+
+
+first_line_re = re.compile(
+    b"([^ ]+) "
+    b"((?:[^ :?#]+://[^ ?#/]*(?:[0-9]{1,5})?)?[^ ]+)"
+    b"(( HTTP/([0-9.]+))$|$)"
+)

--- a/waitress/utilities.py
+++ b/waitress/utilities.py
@@ -1,8 +1,8 @@
-vchar_re = "\x21-\x7e"
+vchar_re = VCHAR

 # RFC 7230 Section 3.2.6 "Field Value Components":
 # quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
 # qdtext        = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
 # obs-text      = %x80-FF
 # quoted-pair   = "\" ( HTAB / SP / VCHAR / obs-text )
-obs_text_re = "\x80-\xff"
+obs_text_re = OBS_TEXT

--- /dev/null
+++ b/waitress/utilities.py
@@ -0,0 +1,44 @@
+"""
+This contains a bunch of RFC7230 definitions and regular expressions that are
+needed to properly parse HTTP messages.
+"""
+
+import re
+
+from .compat import tobytes
+
+WS = "[ \t]"
+OWS = WS + "{0,}?"
+RWS = WS + "{1,}?"
+BWS = OWS
+
+# RFC 7230 Section 3.2.6 "Field Value Components":
+# tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
+#                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
+#                / DIGIT / ALPHA
+# obs-text      = %x80-FF
+TCHAR = r"[!#$%&'*+\-.^_`|~0-9A-Za-z]"
+OBS_TEXT = r"\x80-\xff"
+
+TOKEN = TCHAR + "{1,}"
+
+# RFC 5234 Appendix B.1 "Core Rules":
+# VCHAR         =  %x21-7E
+#                  ; visible (printing) characters
+VCHAR = r"\x21-\x7e"
+
+# header-field   = field-name ":" OWS field-value OWS
+# field-name     = token
+# field-value    = *( field-content / obs-fold )
+# field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+# field-vchar    = VCHAR / obs-text
+
+FIELD_VCHAR = "[" + VCHAR + OBS_TEXT + "]"
+FIELD_CONTENT = FIELD_VCHAR + "(" + RWS + FIELD_VCHAR + "){0,}"
+FIELD_VALUE = "(" + FIELD_CONTENT + "){0,}"
+
+HEADER_FIELD = re.compile(
+    tobytes(
+        "^(?P<name>" + TOKEN + "):" + OWS + "(?P<value>" + FIELD_VALUE + ")" + OWS + "$"
+    )
+)

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

cd /workspace/waitress
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2019-16789/bin/python -m pytest waitress/tests/test_parser.py -k "test_parse_header_invalid_whitespace_vtab or test_parse_header_invalid_no_colon or test_parse_header_invalid_folding_spacing or test_parse_header_invalid_chars or test_parse_header_empty or test_parse_header_multiple_values or test_parse_header_multiple_values_header_folded or test_parse_header_multiple_values_header_folded_multiple" --disable-warnings -v

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

cd /workspace/waitress
git apply --whitespace=nowarn /workspace/test.patch
/workspace/PoC_env/CVE-2019-16789/bin/python -m pytest waitress/tests/test_parser.py -k "not test_parse_header_invalid_chars and not test_parse_header_invalid_folding_spacing and not test_parse_header_invalid_no_colon and not test_parse_header_invalid_whitespace and not test_parse_header_invalid_whitespace_vtab" --disable-warnings


Kyverno is a policy engine designed for cloud native platform engineering teams. Prior to version 1.14.0-alpha.1, Kyverno ignores subjectRegExp and IssuerRegExp while verifying artifact's sign with keyless mode. It allows the attacker to deploy kubernetes resources with the artifacts that were signed by unexpected certificate. Deploying these unauthorized kubernetes resources can lead to full compromise of kubernetes cluster. Version 1.14.0-alpha.1 contains a patch for the issue.

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.

func matchSignatures(signatures []oci.Signature, subject, subjectRegExp, issuer, issuerRegExp string, extensions map[string]string) error {
	if subject == "" && issuer == "" && len(extensions) == 0 {
		return nil
	}

	var errs []error
	for _, sig := range signatures {
		cert, err := sig.Cert()
		if err != nil {
			return fmt.Errorf("failed to read certificate: %w", err)
		}

		if cert == nil {
			return fmt.Errorf("certificate not found")
		}

		if err := matchCertificateData(cert, subject, subjectRegExp, issuer, issuerRegExp, extensions); err != nil {
			errs = append(errs, err)
		} else {
			// only one signature certificate needs to match the required subject, issuer, and extensions
			return nil
		}
	}

	if len(errs) > 0 {
		err := multierr.Combine(errs...)
		return err
	}

	return fmt.Errorf("invalid signature")
}

func matchSignatures(signatures []oci.Signature, subject, subjectRegExp, issuer, issuerRegExp string, extensions map[string]string) error {
	if subject == "" && issuer == "" && subjectRegExp == "" && issuerRegExp == "" && len(extensions) == 0 {
		return nil
	}

--- a/pkg/cosign/cosign.go
+++ b/pkg/cosign/cosign.go
@@ -1,31 +1,4 @@
 func matchSignatures(signatures []oci.Signature, subject, subjectRegExp, issuer, issuerRegExp string, extensions map[string]string) error {
-	if subject == "" && issuer == "" && len(extensions) == 0 {
+	if subject == "" && issuer == "" && subjectRegExp == "" && issuerRegExp == "" && len(extensions) == 0 {
 		return nil
 	}
-
-	var errs []error
-	for _, sig := range signatures {
-		cert, err := sig.Cert()
-		if err != nil {
-			return fmt.Errorf("failed to read certificate: %w", err)
-		}
-
-		if cert == nil {
-			return fmt.Errorf("certificate not found")
-		}
-
-		if err := matchCertificateData(cert, subject, subjectRegExp, issuer, issuerRegExp, extensions); err != nil {
-			errs = append(errs, err)
-		} else {
-			// only one signature certificate needs to match the required subject, issuer, and extensions
-			return nil
-		}
-	}
-
-	if len(errs) > 0 {
-		err := multierr.Combine(errs...)
-		return err
-	}
-
-	return fmt.Errorf("invalid signature")
-}

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

cd /workspace/kyverno
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestCosignMatchSignatures_OnlyRegexpTriggersValidation$ github.com/kyverno/kyverno/pkg/cosign


MJML prior to 4.6.3 contains a path traversal vulnerability when processing the mj-include directive within an MJML document.

  const {
    beautify = false,
    fonts = {
      'Open Sans':
        'https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700',
      'Droid Sans':
        'https://fonts.googleapis.com/css?family=Droid+Sans:300,400,500,700',
      Lato: 'https://fonts.googleapis.com/css?family=Lato:300,400,500,700',
      Roboto: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700',
      Ubuntu: 'https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700',
    },
    keepComments,
    minify = false,
    minifyOptions = {},
    juiceOptions = {},
    juicePreserveTags = null,
    skeleton = defaultSkeleton,
    validationLevel = 'soft',
    filePath = '.',
    actualPath = '.',
    noMigrateWarn = false,
    preprocessors,
  } = {
    ...mjmlConfigOptions,
    ...options,
  }

  if (typeof mjml === 'string') {
    mjml = MJMLParser(mjml, {
      keepComments,
      components,
      filePath,
      actualPath,
      preprocessors,
    })
  }

  const {
    beautify = false,
    fonts = {
      'Open Sans':
        'https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700',
      'Droid Sans':
        'https://fonts.googleapis.com/css?family=Droid+Sans:300,400,500,700',
      Lato: 'https://fonts.googleapis.com/css?family=Lato:300,400,500,700',
      Roboto: 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700',
      Ubuntu: 'https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700',
    },
    keepComments,
    minify = false,
    minifyOptions = {},
    ignoreIncludes = false,
    juiceOptions = {},
    juicePreserveTags = null,
    skeleton = defaultSkeleton,
    validationLevel = 'soft',
    filePath = '.',
    actualPath = '.',
    noMigrateWarn = false,
    preprocessors,
  } = {
    ...mjmlConfigOptions,
    ...options,
  }

  if (typeof mjml === 'string') {
    mjml = MJMLParser(mjml, {
      keepComments,
      components,
      filePath,
      actualPath,
      preprocessors,
      ignoreIncludes,
    })
  }

--- a/packages/mjml-core/src/index.js
+++ b/packages/mjml-core/src/index.js
@@ -12,6 +12,7 @@
     keepComments,
     minify = false,
     minifyOptions = {},
+    ignoreIncludes = false,
     juiceOptions = {},
     juicePreserveTags = null,
     skeleton = defaultSkeleton,

--- a/packages/mjml-core/src/index.js
+++ b/packages/mjml-core/src/index.js
@@ -5,5 +5,6 @@
       filePath,
       actualPath,
       preprocessors,
+      ignoreIncludes,
     })
   }


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

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2022-0691: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 "removes control characters on the left"

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2022-0691: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


URI.js is vulnerable to URL Redirection to Untrusted Site

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

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

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

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

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

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

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

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

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

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

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

--- a/src/URI.js
+++ b/src/URI.js
@@ -22,6 +22,9 @@
       parts.query = string.substring(pos + 1) || null;
       string = string.substring(0, pos);
     }
+
+    // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
+    string = string.replace(/^(https?|ftp|wss?)?:[/\\]*/, '$1://');

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