In Wire before 3.20.x, `shell.openExternal` was used without checking the URL. This vulnerability allows an attacker to execute code on the victims machine by sending messages containing links with arbitrary protocols. The victim has to interact with the link and sees the URL that is opened. The issue was patched by implementing a helper function which checks if the URL's protocol is common. If it is common, the URL will be opened externally. If not, the URL will not be opened and a warning appears for the user informing them that a probably insecure URL was blocked from being executed. The issue is patched in Wire 3.20.x. More technical details about exploitation are available in the linked advisory.

Improper Input Validation

The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.

    aboutWindow.webContents.session.webRequest.onBeforeRequest(async ({url}, callback) => {
      // Only allow those URLs to be opened within the window
      if (ABOUT_WINDOW_ALLOWLIST.includes(url)) {
        return callback({cancel: false});
      }

      // Open HTTPS links in browser instead
      if (url.startsWith('https://')) {
        await shell.openExternal(url);
      } else {
        logger.info(`Attempt to open URL "${url}" in window prevented.`);
        callback({redirectURL: ABOUT_HTML});
      }
    });

  main.webContents.on('new-window', async (event, url) => {
    event.preventDefault();

    // Ensure the link does not come from a webview
    if (typeof (event as any).sender.viewInstanceId !== 'undefined') {
      logger.log('New window was created from a webview, aborting.');
      return;
    }

    await shell.openExternal(url);
  });

  webviewProtection(): void {
    const openLinkInNewWindow = (
      event: ElectronEvent,
      url: string,
      frameName: string,
      _disposition: string,
      options: BrowserWindowConstructorOptions,
    ): Promise<void> => {
      event.preventDefault();

      if (SingleSignOn.isSingleSignOnLoginWindow(frameName)) {
        return new SingleSignOn(main, event, url, options).init();
      }

      this.logger.log('Opening an external window from a webview.');
      return shell.openExternal(url);
    };

const helpTemplate: MenuItemConstructorOptions = {
  label: `&${locale.getText('menuHelp')}`,
  role: 'help',
  submenu: [
    {
      click: () => shell.openExternal(config.legalUrl),
      label: locale.getText('menuLegal'),
    },
    {
      click: () => shell.openExternal(config.privacyUrl),
      label: locale.getText('menuPrivacy'),
    },
    {
      click: () => shell.openExternal(config.licensesUrl),
      label: locale.getText('menuLicense'),
    },
    {
      click: () => shell.openExternal(config.supportUrl),
      label: locale.getText('menuSupport'),
    },
    {
      click: () => shell.openExternal(EnvironmentUtil.web.getWebsiteUrl()),
      label: locale.getText('menuAppURL'),
    },
    downloadLogsTemplate,
  ],
};

    aboutWindow.webContents.session.webRequest.onBeforeRequest(async ({url}, callback) => {
      // Only allow those URLs to be opened within the window
      if (ABOUT_WINDOW_ALLOWLIST.includes(url)) {
        return callback({cancel: false});
      }
    });

    // Handle the new window event in the About Window
    aboutWindow.webContents.on('new-window', (event, url) => {
      event.preventDefault();

      // Ensure the link does not come from a webview
      if (typeof (event as any).sender.viewInstanceId !== 'undefined') {
        logger.log('New window was created from a webview, aborting.');
        return;
      }

      return WindowUtil.openExternal(url, true);
    });

  webviewProtection(): void {
    const openLinkInNewWindow = (
      event: ElectronEvent,
      url: string,
      frameName: string,
      _disposition: string,
      options: BrowserWindowConstructorOptions,
    ): Promise<void> => {
      event.preventDefault();

      if (SingleSignOn.isSingleSignOnLoginWindow(frameName)) {
        return new SingleSignOn(main, event, url, options).init();
      }

      this.logger.log('Opening an external window from a webview.');
      return WindowUtil.openExternal(url);
    };

const helpTemplate: MenuItemConstructorOptions = {
  label: `&${locale.getText('menuHelp')}`,
  role: 'help',
  submenu: [
    {
      click: () => WindowUtil.openExternal(config.legalUrl, true),
      label: locale.getText('menuLegal'),
    },
    {
      click: () => WindowUtil.openExternal(config.privacyUrl, true),
      label: locale.getText('menuPrivacy'),
    },
    {
      click: () => WindowUtil.openExternal(config.licensesUrl, true),
      label: locale.getText('menuLicense'),
    },
    {
      click: () => WindowUtil.openExternal(config.supportUrl, true),
      label: locale.getText('menuSupport'),
    },
    {
      click: () => WindowUtil.openExternal(EnvironmentUtil.web.getWebsiteUrl(), true),
      label: locale.getText('menuAppURL'),
    },
    downloadLogsTemplate,
  ],
};

export const openExternal = async (url: string, httpsOnly: boolean = false): Promise<void> => {
  try {
    const urlProtocol = URL.parse(url).protocol || '';
    const allowedProtocols = ['https:'];

    if (!httpsOnly) {
      allowedProtocols.push('ftp:', 'http:', 'mailto:');
    }

    if (!allowedProtocols.includes(urlProtocol)) {
      logger.warn(`Prevented opening external URL "${url}".`);
      showWarningDialog(`Prevented opening external URL "${url}".`);
      return;
    }

    await shell.openExternal(url);
  } catch (error) {
    logger.error(error);
  }
};

--- a/electron/src/window/AboutWindow.ts
+++ b/electron/src/window/AboutWindow.ts
@@ -3,12 +3,17 @@
       if (ABOUT_WINDOW_ALLOWLIST.includes(url)) {
         return callback({cancel: false});
       }
+    });

-      // Open HTTPS links in browser instead
-      if (url.startsWith('https://')) {
-        await shell.openExternal(url);
-      } else {
-        logger.info(`Attempt to open URL "${url}" in window prevented.`);
-        callback({redirectURL: ABOUT_HTML});
+    // Handle the new window event in the About Window
+    aboutWindow.webContents.on('new-window', (event, url) => {
+      event.preventDefault();
+
+      // Ensure the link does not come from a webview
+      if (typeof (event as any).sender.viewInstanceId !== 'undefined') {
+        logger.log('New window was created from a webview, aborting.');
+        return;
       }
+
+      return WindowUtil.openExternal(url, true);
     });

--- a/electron/src/main.ts
+++ b/electron/src/main.ts
@@ -6,6 +6,3 @@
       logger.log('New window was created from a webview, aborting.');
       return;
     }
-
-    await shell.openExternal(url);
-  });

--- a/electron/src/main.ts
+++ b/electron/src/main.ts
@@ -13,5 +13,5 @@
       }

       this.logger.log('Opening an external window from a webview.');
-      return shell.openExternal(url);
+      return WindowUtil.openExternal(url);
     };

--- a/electron/src/menu/system.ts
+++ b/electron/src/menu/system.ts
@@ -3,23 +3,23 @@
   role: 'help',
   submenu: [
     {
-      click: () => shell.openExternal(config.legalUrl),
+      click: () => WindowUtil.openExternal(config.legalUrl, true),
       label: locale.getText('menuLegal'),
     },
     {
-      click: () => shell.openExternal(config.privacyUrl),
+      click: () => WindowUtil.openExternal(config.privacyUrl, true),
       label: locale.getText('menuPrivacy'),
     },
     {
-      click: () => shell.openExternal(config.licensesUrl),
+      click: () => WindowUtil.openExternal(config.licensesUrl, true),
       label: locale.getText('menuLicense'),
     },
     {
-      click: () => shell.openExternal(config.supportUrl),
+      click: () => WindowUtil.openExternal(config.supportUrl, true),
       label: locale.getText('menuSupport'),
     },
     {
-      click: () => shell.openExternal(EnvironmentUtil.web.getWebsiteUrl()),
+      click: () => WindowUtil.openExternal(EnvironmentUtil.web.getWebsiteUrl(), true),
       label: locale.getText('menuAppURL'),
     },
     downloadLogsTemplate,

--- /dev/null
+++ b/electron/src/menu/system.ts
@@ -0,0 +1,20 @@
+export const openExternal = async (url: string, httpsOnly: boolean = false): Promise<void> => {
+  try {
+    const urlProtocol = URL.parse(url).protocol || '';
+    const allowedProtocols = ['https:'];
+
+    if (!httpsOnly) {
+      allowedProtocols.push('ftp:', 'http:', 'mailto:');
+    }
+
+    if (!allowedProtocols.includes(urlProtocol)) {
+      logger.warn(`Prevented opening external URL "${url}".`);
+      showWarningDialog(`Prevented opening external URL "${url}".`);
+      return;
+    }
+
+    await shell.openExternal(url);
+  } catch (error) {
+    logger.error(error);
+  }
+};


A vulnerability was found in openstack-ironic-inspector all versions excluding 5.0.2, 6.0.3, 7.2.4, 8.0.3 and 8.2.1. A SQL-injection vulnerability was found in openstack-ironic-inspector's node_cache.find_node(). This function makes a SQL query using unfiltered data from a server reporting inspection results (by a POST to the /v1/continue endpoint). Because the API is unauthenticated, the flaw could be exploited by an attacker with access to the network on which ironic-inspector is listening. Because of how ironic-inspector uses the query results, it is unlikely that data could be obtained. However, the attacker could pass malicious data and create a denial of service.

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

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

def find_node(**attributes):
    """Find node in cache.

    Looks up a node based on attributes in a best-match fashion.
    This function acquires a lock on a node.

    :param attributes: attributes known about this node (like macs, BMC etc)
                       also ironic client instance may be passed under 'ironic'
    :returns: structure NodeInfo with attributes ``uuid`` and ``created_at``
    :raises: Error if node is not found or multiple nodes match the attributes
    """
    ironic = attributes.pop('ironic', None)
    # NOTE(dtantsur): sorting is not required, but gives us predictability
    found = collections.Counter()

    for (name, value) in sorted(attributes.items()):
        if not value:
            LOG.debug('Empty value for attribute %s', name)
            continue
        if not isinstance(value, list):
            value = [value]

        LOG.debug('Trying to use %s of value %s for node look up',
                  name, value)
        value_list = []
        for v in value:
            value_list.append("name='%s' AND value='%s'" % (name, v))
        stmt = ('select distinct node_uuid from attributes where ' +
                ' OR '.join(value_list))
        rows = (db.model_query(db.Attribute.node_uuid).from_statement(
            text(stmt)).all())
        found.update(row.node_uuid for row in rows)

    if not found:
        raise utils.NotFoundInCacheError(_(
            'Could not find a node for attributes %s') % attributes)

    most_common = found.most_common()
    LOG.debug('The following nodes match the attributes: %(attributes)s, '
              'scoring: %(most_common)s',
              {'most_common': ', '.join('%s: %d' % tpl for tpl in most_common),
               'attributes': ', '.join('%s=%s' % tpl for tpl in
                                       attributes.items())})

    # NOTE(milan) most_common is sorted, higher scores first
    highest_score = most_common[0][1]
    found = [item[0] for item in most_common if highest_score == item[1]]
    if len(found) > 1:
        raise utils.Error(_(
            'Multiple nodes match the same number of attributes '
            '%(attr)s: %(found)s')
            % {'attr': attributes, 'found': found}, code=404)

    uuid = found.pop()
    node_info = NodeInfo(uuid=uuid, ironic=ironic)
    node_info.acquire_lock()

    try:
        row = (db.model_query(db.Node.started_at, db.Node.finished_at).
               filter_by(uuid=uuid).first())

        if not row:
            raise utils.Error(_(
                'Could not find node %s in introspection cache, '
                'probably it\'s not on introspection now') % uuid, code=404)

        if row.finished_at:
            raise utils.Error(_(
                'Introspection for node %(node)s already finished on '
                '%(finish)s') % {'node': uuid, 'finish': row.finished_at})

        node_info.started_at = row.started_at
        return node_info
    except Exception:
        with excutils.save_and_reraise_exception():
            node_info.release_lock()

def find_node(**attributes):
    """Find node in cache.

    Looks up a node based on attributes in a best-match fashion.
    This function acquires a lock on a node.

    :param attributes: attributes known about this node (like macs, BMC etc)
                       also ironic client instance may be passed under 'ironic'
    :returns: structure NodeInfo with attributes ``uuid`` and ``created_at``
    :raises: Error if node is not found or multiple nodes match the attributes
    """
    ironic = attributes.pop('ironic', None)
    # NOTE(dtantsur): sorting is not required, but gives us predictability
    found = collections.Counter()

    for (name, value) in sorted(attributes.items()):
        if not value:
            LOG.debug('Empty value for attribute %s', name)
            continue
        if not isinstance(value, list):
            value = [value]

        LOG.debug('Trying to use %s of value %s for node look up',
                  name, value)
        query = db.model_query(db.Attribute.node_uuid)
        pairs = [(db.Attribute.name == name) &
                 (db.Attribute.value == v) for v in value]
        query = query.filter(six.moves.reduce(operator.or_, pairs))
        found.update(row.node_uuid for row in query.distinct().all())

    if not found:
        raise utils.NotFoundInCacheError(_(
            'Could not find a node for attributes %s') % attributes)

    most_common = found.most_common()
    LOG.debug('The following nodes match the attributes: %(attributes)s, '
              'scoring: %(most_common)s',
              {'most_common': ', '.join('%s: %d' % tpl for tpl in most_common),
               'attributes': ', '.join('%s=%s' % tpl for tpl in
                                       attributes.items())})

    # NOTE(milan) most_common is sorted, higher scores first
    highest_score = most_common[0][1]
    found = [item[0] for item in most_common if highest_score == item[1]]
    if len(found) > 1:
        raise utils.Error(_(
            'Multiple nodes match the same number of attributes '
            '%(attr)s: %(found)s')
            % {'attr': attributes, 'found': found}, code=404)

    uuid = found.pop()
    node_info = NodeInfo(uuid=uuid, ironic=ironic)
    node_info.acquire_lock()

    try:
        row = (db.model_query(db.Node.started_at, db.Node.finished_at).
               filter_by(uuid=uuid).first())

        if not row:
            raise utils.Error(_(
                'Could not find node %s in introspection cache, '
                'probably it\'s not on introspection now') % uuid, code=404)

        if row.finished_at:
            raise utils.Error(_(
                'Introspection for node %(node)s already finished on '
                '%(finish)s') % {'node': uuid, 'finish': row.finished_at})

        node_info.started_at = row.started_at
        return node_info
    except Exception:
        with excutils.save_and_reraise_exception():
            node_info.release_lock()

--- a/ironic_inspector/node_cache.py
+++ b/ironic_inspector/node_cache.py
@@ -22,14 +22,11 @@

         LOG.debug('Trying to use %s of value %s for node look up',
                   name, value)
-        value_list = []
-        for v in value:
-            value_list.append("name='%s' AND value='%s'" % (name, v))
-        stmt = ('select distinct node_uuid from attributes where ' +
-                ' OR '.join(value_list))
-        rows = (db.model_query(db.Attribute.node_uuid).from_statement(
-            text(stmt)).all())
-        found.update(row.node_uuid for row in rows)
+        query = db.model_query(db.Attribute.node_uuid)
+        pairs = [(db.Attribute.name == name) &
+                 (db.Attribute.value == v) for v in value]
+        query = query.filter(six.moves.reduce(operator.or_, pairs))
+        found.update(row.node_uuid for row in query.distinct().all())

     if not found:
         raise utils.NotFoundInCacheError(_(


github.com/sylabs/scs-library-client is the Go client for the Singularity Container Services (SCS) Container Library Service. When the scs-library-client is used to pull a container image, with authentication, the HTTP Authorization header sent by the client to the library service may be incorrectly leaked to an S3 backing storage provider. This occurs in a specific flow, where the library service redirects the client to a backing S3 storage server, to perform a multi-part concurrent download. Depending on site configuration, the S3 service may be provided by a third party. An attacker with access to the S3 service may be able to extract user credentials, allowing them to impersonate the user. The vulnerable multi-part concurrent download flow, with redirect to S3, is only used when communicating with a Singularity Enterprise 1.x installation, or third party server implementing this flow. Interaction with Singularity Enterprise 2.x, and Singularity Container Services (cloud.sylabs.io), does not trigger the vulnerable flow. We encourage all users to update. Users who interact with a Singularity Enterprise 1.x installation, using a 3rd party S3 storage service, are advised to revoke and recreate their authentication tokens within Singularity Enterprise. There is no workaround available at this time.

Insufficiently Protected Credentials

The product transmits or stores authentication credentials, but it uses an insecure method that is susceptible to unauthorized interception and/or retrieval.

func (c *Client) httpGetRangeRequest(ctx context.Context, url string, start, end int64) (*http.Response, error) {
	req, err := c.newRequestWithURL(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, end))

	return c.HTTPClient.Do(req)
}

func (c *Client) httpGetRangeRequest(ctx context.Context, url string, start, end int64) (*http.Response, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}

	if v := c.UserAgent; v != "" {
		req.Header.Set("User-Agent", v)
	}

	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, end))

	return c.HTTPClient.Do(req)
}

--- a/client/pull.go
+++ b/client/pull.go
@@ -1,7 +1,11 @@
 func (c *Client) httpGetRangeRequest(ctx context.Context, url string, start, end int64) (*http.Response, error) {
-	req, err := c.newRequestWithURL(ctx, http.MethodGet, url, nil)
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
 	if err != nil {
 		return nil, err
+	}
+
+	if v := c.UserAgent; v != "" {
+		req.Header.Set("User-Agent", v)
 	}

 	req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, end))

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

cd /workspace/scs-library-client
rm -rf ./client/pull_test.go
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^Test_httpGetRangeRequest_NoAuthHeader$ github.com/sylabs/scs-library-client/client

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

cd /workspace/scs-library-client
git apply --whitespace=nowarn /workspace/fix.patch
cd client && go test -timeout 30s -v github.com/sylabs/scs-library-client/client


Improper Access Control in GitHub repository ikus060/rdiffweb prior to 2.5.5.

Improper Access Control

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

@event.listens_for(Base.metadata, 'after_create')
def db_after_create(target, connection, **kw):
    """
    Called on database creation to update database schema.
    """

    if getattr(connection, '_transaction', None):
        connection._transaction.commit()

    # Add repo's Encoding
    _column_add(connection, RepoObject.__table__.c.Encoding)
    _column_add(connection, RepoObject.__table__.c.keepdays)

    # Create column for roles using "isadmin" column. Keep the
    # original column in case we need to revert to previous version.
    if not _column_exists(connection, UserObject.__table__.c.role):
        _column_add(connection, UserObject.__table__.c.role)
        UserObject.query.filter(UserObject._is_admin == 1).update({UserObject.role: UserObject.ADMIN_ROLE})

    # Add user's fullname column
    _column_add(connection, UserObject.__table__.c.fullname)

    # Add user's mfa column
    _column_add(connection, UserObject.__table__.c.mfa)

    # Re-create session table if Number column is missing
    if not _column_exists(connection, SessionObject.__table__.c.Number):
        SessionObject.__table__.drop()
        SessionObject.__table__.create()

    if getattr(connection, '_transaction', None):
        connection._transaction.commit()

    # Remove preceding and leading slash (/) generated by previous
    # versions. Also rename '.' to ''
    result = RepoObject.query.all()
    for row in result:
        if row.repopath.startswith('/') or row.repopath.endswith('/'):
            row.repopath = row.repopath.strip('/')
            row.commit()
        if row.repopath == '.':
            row.repopath = ''
            row.commit()
    # Remove duplicates and nested repositories.
    result = RepoObject.query.order_by(RepoObject.userid, RepoObject.repopath).all()
    prev_repo = (None, None)
    for row in result:
        if prev_repo[0] == row.userid and (prev_repo[1] == row.repopath or row.repopath.startswith(prev_repo[1] + '/')):
            row.delete()
        else:
            prev_repo = (row.userid, row.repopath)

    # Fix username case insensitive unique
    if not _index_exists(connection, 'user_username_index'):
        duplicate_users = (
            UserObject.query.with_entities(func.lower(UserObject.username))
            .group_by(func.lower(UserObject.username))
            .having(func.count(UserObject.username) > 1)
        ).all()
        try:
            user_username_index.create()
        except IntegrityError:
            msg = (
                'Failure to upgrade your database to make Username case insensitive. '
                'You must downgrade and deleted duplicate Username. '
                '%s' % '\n'.join([str(k) for k in duplicate_users]),
            )
            logger.error(msg)
            print(msg, file=sys.stderr)
            raise SystemExit(12)

    def add_authorizedkey(self, key, comment=None):
        """
        Add the given key to the user. Adding the key to his `authorized_keys`
        file if it exists and adding it to database.
        """
        # Parse and validate ssh key
        assert key
        key = authorizedkeys.check_publickey(key)

        # Remove option, replace comments.
        key = authorizedkeys.AuthorizedKey(
            options=None, keytype=key.keytype, key=key.key, comment=comment or key.comment
        )

        # If a filename exists, use it by default.
        filename = os.path.join(self.user_root, '.ssh', 'authorized_keys')
        if os.path.isfile(filename):
            with open(filename, mode="r+", encoding='utf-8') as fh:
                if authorizedkeys.exists(fh, key):
                    raise DuplicateSSHKeyError(_("SSH key already exists"))
                logger.info("add key [%s] to [%s] authorized_keys", key, self.username)
                authorizedkeys.add(fh, key)
        else:
            # Also look in database.
            logger.info("add key [%s] to [%s] database", key, self.username)
            try:
                SshKey(userid=self.userid, fingerprint=key.fingerprint, key=key.getvalue()).add().flush()
            except IntegrityError:
                raise DuplicateSSHKeyError(
                    _("Duplicate key. This key already exists or is associated to another user.")
                )
        cherrypy.engine.publish('user_attr_changed', self, {'authorizedkeys': True})
        cherrypy.engine.publish('authorizedkey_added', self, fingerprint=key.fingerprint, comment=comment)

def db_after_create(target, connection, **kw):
    """
    Called on database creation to update database schema.
    """

    if getattr(connection, '_transaction', None):
        connection._transaction.commit()

    # Add repo's Encoding
    _column_add(connection, RepoObject.__table__.c.Encoding)
    _column_add(connection, RepoObject.__table__.c.keepdays)

    # Create column for roles using "isadmin" column. Keep the
    # original column in case we need to revert to previous version.
    if not _column_exists(connection, UserObject.__table__.c.role):
        _column_add(connection, UserObject.__table__.c.role)
        UserObject.query.filter(UserObject._is_admin == 1).update({UserObject.role: UserObject.ADMIN_ROLE})

    # Add user's fullname column
    _column_add(connection, UserObject.__table__.c.fullname)

    # Add user's mfa column
    _column_add(connection, UserObject.__table__.c.mfa)

    # Re-create session table if Number column is missing
    if not _column_exists(connection, SessionObject.__table__.c.Number):
        SessionObject.__table__.drop()
        SessionObject.__table__.create()

    if getattr(connection, '_transaction', None):
        connection._transaction.commit()

    # Remove preceding and leading slash (/) generated by previous
    # versions. Also rename '.' to ''
    result = RepoObject.query.all()
    for row in result:
        if row.repopath.startswith('/') or row.repopath.endswith('/'):
            row.repopath = row.repopath.strip('/')
            row.commit()
        if row.repopath == '.':
            row.repopath = ''
            row.commit()
    # Remove duplicates and nested repositories.
    result = RepoObject.query.order_by(RepoObject.userid, RepoObject.repopath).all()
    prev_repo = (None, None)
    for row in result:
        if prev_repo[0] == row.userid and (prev_repo[1] == row.repopath or row.repopath.startswith(prev_repo[1] + '/')):
            row.delete()
        else:
            prev_repo = (row.userid, row.repopath)

    # Fix username case insensitive unique
    if not _index_exists(connection, 'user_username_index'):
        duplicate_users = (
            UserObject.query.with_entities(func.lower(UserObject.username))
            .group_by(func.lower(UserObject.username))
            .having(func.count(UserObject.username) > 1)
        ).all()
        try:
            user_username_index.create()
        except IntegrityError:
            msg = (
                'Failure to upgrade your database to make Username case insensitive. '
                'You must downgrade and deleted duplicate Username. '
                '%s' % '\n'.join([str(k) for k in duplicate_users]),
            )
            logger.error(msg)
            print(msg, file=sys.stderr)
            raise SystemExit(12)

    # Fix SSH Key uniqueness - since 2.5.4
    if not _index_exists(connection, 'sshkey_fingerprint_index'):
        duplicate_sshkeys = (
            SshKey.query.with_entities(SshKey.fingerprint)
            .group_by(SshKey.fingerprint)
            .having(func.count(SshKey.fingerprint) > 1)
        ).all()
        try:
            sshkey_fingerprint_index.create()
        except IntegrityError:
            msg = (
                'Failure to upgrade your database to make SSH Keys unique. '
                'You must downgrade and deleted duplicate SSH Keys. '
                '%s' % '\n'.join([str(k) for k in duplicate_sshkeys]),
            )
            logger.error(msg)
            print(msg, file=sys.stderr)
            raise SystemExit(12)

class SshKey(Base):
    __tablename__ = 'sshkeys'
    __table_args__ = {'sqlite_autoincrement': True}
    fingerprint = Column('Fingerprint', Text)
    key = Column('Key', Text, unique=True, primary_key=True)
    userid = Column('UserID', Integer, nullable=False)

# Make finger print unique
sshkey_fingerprint_index = Index('sshkey_fingerprint_index', SshKey.fingerprint, unique=True)

    def add_authorizedkey(self, key, comment=None):
        """
        Add the given key to the user. Adding the key to his `authorized_keys`
        file if it exists and adding it to database.
        """
        # Parse and validate ssh key
        assert key
        key = authorizedkeys.check_publickey(key)

        # Remove option & Remove comment for SQL storage
        key = authorizedkeys.AuthorizedKey(
            options=None, keytype=key.keytype, key=key.key, comment=comment or key.comment
        )

        # If a filename exists, use it by default.
        filename = os.path.join(self.user_root, '.ssh', 'authorized_keys')
        if os.path.isfile(filename):
            with open(filename, mode="r+", encoding='utf-8') as fh:
                if authorizedkeys.exists(fh, key):
                    raise DuplicateSSHKeyError(_("SSH key already exists"))
                logger.info("add key [%s] to [%s] authorized_keys", key, self.username)
                authorizedkeys.add(fh, key)
        else:
            # Also look in database.
            logger.info("add key [%s] to [%s] database", key, self.username)
            try:
                sshkey = SshKey(userid=self.userid, fingerprint=key.fingerprint, key=key.getvalue())
                sshkey.add().flush()
            except IntegrityError:
                raise DuplicateSSHKeyError(
                    _("Duplicate key. This key already exists or is associated to another user.")
                )
        cherrypy.engine.publish('user_attr_changed', self, {'authorizedkeys': True})
        cherrypy.engine.publish('authorizedkey_added', self, fingerprint=key.fingerprint, comment=comment)

--- a/rdiffweb/core/model/__init__.py
+++ b/rdiffweb/core/model/__init__.py
@@ -1,4 +1,3 @@
-@event.listens_for(Base.metadata, 'after_create')
 def db_after_create(target, connection, **kw):
     """
     Called on database creation to update database schema.
@@ -68,3 +67,22 @@
             logger.error(msg)
             print(msg, file=sys.stderr)
             raise SystemExit(12)
+
+    # Fix SSH Key uniqueness - since 2.5.4
+    if not _index_exists(connection, 'sshkey_fingerprint_index'):
+        duplicate_sshkeys = (
+            SshKey.query.with_entities(SshKey.fingerprint)
+            .group_by(SshKey.fingerprint)
+            .having(func.count(SshKey.fingerprint) > 1)
+        ).all()
+        try:
+            sshkey_fingerprint_index.create()
+        except IntegrityError:
+            msg = (
+                'Failure to upgrade your database to make SSH Keys unique. '
+                'You must downgrade and deleted duplicate SSH Keys. '
+                '%s' % '\n'.join([str(k) for k in duplicate_sshkeys]),
+            )
+            logger.error(msg)
+            print(msg, file=sys.stderr)
+            raise SystemExit(12)

--- a/rdiffweb/core/model/_user.py
+++ b/rdiffweb/core/model/_sshkey.py
@@ -1,33 +1,10 @@
-    def add_authorizedkey(self, key, comment=None):
-        """
-        Add the given key to the user. Adding the key to his `authorized_keys`
-        file if it exists and adding it to database.
-        """
-        # Parse and validate ssh key
-        assert key
-        key = authorizedkeys.check_publickey(key)
+class SshKey(Base):
+    __tablename__ = 'sshkeys'
+    __table_args__ = {'sqlite_autoincrement': True}
+    fingerprint = Column('Fingerprint', Text)
+    key = Column('Key', Text, unique=True, primary_key=True)
+    userid = Column('UserID', Integer, nullable=False)

-        # Remove option, replace comments.
-        key = authorizedkeys.AuthorizedKey(
-            options=None, keytype=key.keytype, key=key.key, comment=comment or key.comment
-        )

-        # If a filename exists, use it by default.
-        filename = os.path.join(self.user_root, '.ssh', 'authorized_keys')
-        if os.path.isfile(filename):
-            with open(filename, mode="r+", encoding='utf-8') as fh:
-                if authorizedkeys.exists(fh, key):
-                    raise DuplicateSSHKeyError(_("SSH key already exists"))
-                logger.info("add key [%s] to [%s] authorized_keys", key, self.username)
-                authorizedkeys.add(fh, key)
-        else:
-            # Also look in database.
-            logger.info("add key [%s] to [%s] database", key, self.username)
-            try:
-                SshKey(userid=self.userid, fingerprint=key.fingerprint, key=key.getvalue()).add().flush()
-            except IntegrityError:
-                raise DuplicateSSHKeyError(
-                    _("Duplicate key. This key already exists or is associated to another user.")
-                )
-        cherrypy.engine.publish('user_attr_changed', self, {'authorizedkeys': True})
-        cherrypy.engine.publish('authorizedkey_added', self, fingerprint=key.fingerprint, comment=comment)
+# Make finger print unique
+sshkey_fingerprint_index = Index('sshkey_fingerprint_index', SshKey.fingerprint, unique=True)

--- /dev/null
+++ b/rdiffweb/core/model/_sshkey.py
@@ -0,0 +1,34 @@
+    def add_authorizedkey(self, key, comment=None):
+        """
+        Add the given key to the user. Adding the key to his `authorized_keys`
+        file if it exists and adding it to database.
+        """
+        # Parse and validate ssh key
+        assert key
+        key = authorizedkeys.check_publickey(key)
+
+        # Remove option & Remove comment for SQL storage
+        key = authorizedkeys.AuthorizedKey(
+            options=None, keytype=key.keytype, key=key.key, comment=comment or key.comment
+        )
+
+        # If a filename exists, use it by default.
+        filename = os.path.join(self.user_root, '.ssh', 'authorized_keys')
+        if os.path.isfile(filename):
+            with open(filename, mode="r+", encoding='utf-8') as fh:
+                if authorizedkeys.exists(fh, key):
+                    raise DuplicateSSHKeyError(_("SSH key already exists"))
+                logger.info("add key [%s] to [%s] authorized_keys", key, self.username)
+                authorizedkeys.add(fh, key)
+        else:
+            # Also look in database.
+            logger.info("add key [%s] to [%s] database", key, self.username)
+            try:
+                sshkey = SshKey(userid=self.userid, fingerprint=key.fingerprint, key=key.getvalue())
+                sshkey.add().flush()
+            except IntegrityError:
+                raise DuplicateSSHKeyError(
+                    _("Duplicate key. This key already exists or is associated to another user.")
+                )
+        cherrypy.engine.publish('user_attr_changed', self, {'authorizedkeys': True})
+        cherrypy.engine.publish('authorizedkey_added', self, fingerprint=key.fingerprint, comment=comment)

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

cd /workspace/rdiffweb
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2022-4724/bin/python -m pytest rdiffweb/core/model/tests/test_user.py -k "test_add_authorizedkey_duplicate" -p no:warning --disable-warnings --import-mode=importlib

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

cd /workspace/rdiffweb
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2022-4724/bin/python -m pytest rdiffweb/core/model/tests/test_user.py  -p no:warning --disable-warnings --import-mode=importlib


parse-server is a Parse Server for Node.js / Express. This vulnerability allows SQL injection when Parse Server is configured to use the PostgreSQL database. The vulnerability has been fixed in 6.5.0 and 7.0.0-alpha.20.

function literalizeRegexPart(s: string) {
  const matcher1 = /\\Q((?!\\E).*)\\E$/;
  const result1: any = s.match(matcher1);
  if (result1 && result1.length > 1 && result1.index > -1) {
    // process regex that has a beginning and an end specified for the literal text
    const prefix = s.substring(0, result1.index);
    const remaining = result1[1];

    return literalizeRegexPart(prefix) + createLiteralRegex(remaining);
  }

  // process regex that has a beginning specified for the literal text
  const matcher2 = /\\Q((?!\\E).*)$/;
  const result2: any = s.match(matcher2);
  if (result2 && result2.length > 1 && result2.index > -1) {
    const prefix = s.substring(0, result2.index);
    const remaining = result2[1];

    return literalizeRegexPart(prefix) + createLiteralRegex(remaining);
  }

  // remove all instances of \Q and \E from the remaining text & escape single quotes
  return s
    .replace(/([^\\])(\\E)/, '$1')
    .replace(/([^\\])(\\Q)/, '$1')
    .replace(/^\\E/, '')
    .replace(/^\\Q/, '')
    .replace(/([^'])'/, `$1''`)
    .replace(/^'([^'])/, `''$1`);
}

function literalizeRegexPart(s: string) {
  const matcher1 = /\\Q((?!\\E).*)\\E$/;
  const result1: any = s.match(matcher1);
  if (result1 && result1.length > 1 && result1.index > -1) {
    // process regex that has a beginning and an end specified for the literal text
    const prefix = s.substring(0, result1.index);
    const remaining = result1[1];

    return literalizeRegexPart(prefix) + createLiteralRegex(remaining);
  }

  // process regex that has a beginning specified for the literal text
  const matcher2 = /\\Q((?!\\E).*)$/;
  const result2: any = s.match(matcher2);
  if (result2 && result2.length > 1 && result2.index > -1) {
    const prefix = s.substring(0, result2.index);
    const remaining = result2[1];

    return literalizeRegexPart(prefix) + createLiteralRegex(remaining);
  }

  // remove all instances of \Q and \E from the remaining text & escape single quotes
  return s
    .replace(/([^\\])(\\E)/, '$1')
    .replace(/([^\\])(\\Q)/, '$1')
    .replace(/^\\E/, '')
    .replace(/^\\Q/, '')
    .replace(/([^'])'/g, `$1''`)
    .replace(/^'([^'])/, `''$1`);
}

--- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
+++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
@@ -25,6 +25,6 @@
     .replace(/([^\\])(\\Q)/, '$1')
     .replace(/^\\E/, '')
     .replace(/^\\Q/, '')
-    .replace(/([^'])'/, `$1''`)
+    .replace(/([^'])'/g, `$1''`)
     .replace(/^'([^'])/, `''$1`);
 }


HashiCorp Nomad and Nomad Enterprise versions 1.5.0 up to 1.5.2 allow unauthenticated users to bypass intended ACL authorizations for clusters where mTLS is not enabled. This issue is fixed in version 1.5.3.

Missing Authorization

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

func (s *Server) Authenticate(ctx *RPCContext, args structs.RequestWithIdentity) error {

	// get the user ACLToken or anonymous token
	secretID := args.GetAuthToken()
	aclToken, err := s.ResolveSecretToken(secretID)

	switch {
	case err == nil:
		// If ACLs are disabled or we have a non-anonymous token, return that.
		if aclToken == nil || aclToken != structs.AnonymousACLToken {
			args.SetIdentity(&structs.AuthenticatedIdentity{ACLToken: aclToken})
			return nil
		}

	case errors.Is(err, structs.ErrTokenExpired):
		return err

	case errors.Is(err, structs.ErrTokenInvalid):
		// if it's not a UUID it might be an identity claim
		claims, err := s.VerifyClaim(secretID)
		if err != nil {
			// we already know the token wasn't valid for an ACL in the state
			// store, so if we get an error at this point we have an invalid
			// token and there are no other options but to bail out
			return err
		}

		args.SetIdentity(&structs.AuthenticatedIdentity{Claims: claims})
		return nil

	case errors.Is(err, structs.ErrTokenNotFound):
		// Check if the secret ID is the leader's secret ID, in which case treat
		// it as a management token.
		leaderAcl := s.getLeaderAcl()
		if leaderAcl != "" && secretID == leaderAcl {
			aclToken = structs.LeaderACLToken
			break
		} else {
			// Otherwise, see if the secret ID belongs to a node. We should
			// reach this point only on first connection.
			node, err := s.State().NodeBySecretID(nil, secretID)
			if err != nil {
				// this is a go-memdb error; shouldn't happen
				return fmt.Errorf("could not resolve node secret: %w", err)
			}
			if node != nil {
				args.SetIdentity(&structs.AuthenticatedIdentity{ClientID: node.ID})
				return nil
			}
		}

		// we were passed a bogus token so we'll return an error, but we'll also
		// want to capture the IP for metrics
		remoteIP, err := s.remoteIPFromRPCContext(ctx)
		if err != nil {
			s.logger.Error("could not determine remote address", "error", err)
		}
		args.SetIdentity(&structs.AuthenticatedIdentity{RemoteIP: remoteIP})
		return structs.ErrPermissionDenied

	default: // any other error
		return fmt.Errorf("could not resolve user: %w", err)

	}

	// If there's no context we're in a "static" handler which only happens for
	// cases where the leader is making RPCs internally (volumewatcher and
	// deploymentwatcher)
	if ctx == nil {
		args.SetIdentity(&structs.AuthenticatedIdentity{ACLToken: aclToken})
		return nil
	}

	// At this point we either have an anonymous token or an invalid one.

	// Previously-connected clients will have a NodeID set on the context, which
	// is available for all yamux streams over the same yamux session (and TCP
	// connection). This will be a large portion of the RPCs sent, but we can't
	// fast-path this at the top of the method, because authenticated HTTP
	// requests to the clients will come in over to the same session.
	if ctx.NodeID != "" {
		args.SetIdentity(&structs.AuthenticatedIdentity{ClientID: ctx.NodeID})
		return nil
	}

	// Unlike clients that provide their Node ID on first connection, server
	// RPCs don't include an ID for the server so we identify servers by cert
	// and IP address.
	identity := &structs.AuthenticatedIdentity{ACLToken: aclToken}
	if ctx.TLS {
		identity.TLSName = ctx.Certificate().Subject.CommonName
	}

	remoteIP, err := s.remoteIPFromRPCContext(ctx)
	if err != nil {
		s.logger.Error(
			"could not authenticate RPC request or determine remote address", "error", err)
		return err
	}
	identity.RemoteIP = remoteIP
	args.SetIdentity(identity)
	return nil
}

func (s *Server) Authenticate(ctx *RPCContext, args structs.RequestWithIdentity) error {

	// get the user ACLToken or anonymous token
	secretID := args.GetAuthToken()
	aclToken, err := s.ResolveSecretToken(secretID)

	switch {
	case err == nil:
		// If ACLs are disabled or we have a non-anonymous token, return that.
		if aclToken == nil || aclToken != structs.AnonymousACLToken {
			args.SetIdentity(&structs.AuthenticatedIdentity{ACLToken: aclToken})
			return nil
		}

	case errors.Is(err, structs.ErrTokenExpired):
		return err

	case errors.Is(err, structs.ErrTokenInvalid):
		// if it's not a UUID it might be an identity claim
		claims, err := s.VerifyClaim(secretID)
		if err != nil {
			// we already know the token wasn't valid for an ACL in the state
			// store, so if we get an error at this point we have an invalid
			// token and there are no other options but to bail out
			return err
		}

		args.SetIdentity(&structs.AuthenticatedIdentity{Claims: claims})
		return nil

	case errors.Is(err, structs.ErrTokenNotFound):
		// Check if the secret ID is the leader's secret ID, in which case treat
		// it as a management token.
		leaderAcl := s.getLeaderAcl()
		if leaderAcl != "" && secretID == leaderAcl {
			aclToken = structs.LeaderACLToken
			break
		} else {
			// Otherwise, see if the secret ID belongs to a node. We should
			// reach this point only on first connection.
			node, err := s.State().NodeBySecretID(nil, secretID)
			if err != nil {
				// this is a go-memdb error; shouldn't happen
				return fmt.Errorf("could not resolve node secret: %w", err)
			}
			if node != nil {
				args.SetIdentity(&structs.AuthenticatedIdentity{ClientID: node.ID})
				return nil
			}
		}

		// we were passed a bogus token so we'll return an error, but we'll also
		// want to capture the IP for metrics
		remoteIP, err := s.remoteIPFromRPCContext(ctx)
		if err != nil {
			s.logger.Error("could not determine remote address", "error", err)
		}
		args.SetIdentity(&structs.AuthenticatedIdentity{RemoteIP: remoteIP})
		return structs.ErrPermissionDenied

	default: // any other error
		return fmt.Errorf("could not resolve user: %w", err)

	}

	// If there's no context we're in a "static" handler which only happens for
	// cases where the leader is making RPCs internally (volumewatcher and
	// deploymentwatcher)
	if ctx == nil {
		args.SetIdentity(&structs.AuthenticatedIdentity{ACLToken: aclToken})
		return nil
	}

	// At this point we either have an anonymous token or an invalid one.

	// TODO(tgross): remove this entirely in 1.6.0 and enforce that all RPCs
	// driven by the clients have secret IDs set
	if ctx.NodeID != "" && secretID != "" {
		args.SetIdentity(&structs.AuthenticatedIdentity{ClientID: ctx.NodeID})
		return nil
	}

	// Unlike clients that provide their Node ID on first connection, server
	// RPCs don't include an ID for the server so we identify servers by cert
	// and IP address.
	identity := &structs.AuthenticatedIdentity{ACLToken: aclToken}
	if ctx.TLS {
		identity.TLSName = ctx.Certificate().Subject.CommonName
	}

	remoteIP, err := s.remoteIPFromRPCContext(ctx)
	if err != nil {
		s.logger.Error(
			"could not authenticate RPC request or determine remote address", "error", err)
		return err
	}
	identity.RemoteIP = remoteIP
	args.SetIdentity(identity)
	return nil
}

--- a/nomad/acl.go
+++ b/nomad/acl.go
@@ -73,12 +73,9 @@

 	// At this point we either have an anonymous token or an invalid one.

-	// Previously-connected clients will have a NodeID set on the context, which
-	// is available for all yamux streams over the same yamux session (and TCP
-	// connection). This will be a large portion of the RPCs sent, but we can't
-	// fast-path this at the top of the method, because authenticated HTTP
-	// requests to the clients will come in over to the same session.
-	if ctx.NodeID != "" {
+	// TODO(tgross): remove this entirely in 1.6.0 and enforce that all RPCs
+	// driven by the clients have secret IDs set
+	if ctx.NodeID != "" && secretID != "" {
 		args.SetIdentity(&structs.AuthenticatedIdentity{ClientID: ctx.NodeID})
 		return nil
 	}


A directory traversal vulnerability in the /get-artifact API method of the mlflow platform up to v2.0.1 allows attackers to read arbitrary files on the server via the path parameter.

External Control of File Name or Path

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

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

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

def get_artifact_handler():
    from querystring_parser import parser

    query_string = request.query_string.decode("utf-8")
    request_dict = parser.parse(query_string, normalized=True)
    run_id = request_dict.get("run_id") or request_dict.get("run_uuid")
    run = _get_tracking_store().get_run(run_id)

    if _is_servable_proxied_run_artifact_root(run.info.artifact_uri):
        artifact_repo = _get_artifact_repo_mlflow_artifacts()
        artifact_path = _get_proxied_run_artifact_destination_path(
            proxied_artifact_root=run.info.artifact_uri,
            relative_path=request_dict["path"],
        )
    else:
        artifact_repo = _get_artifact_repo(run)
        artifact_path = request_dict["path"]

    return _send_artifact(artifact_repo, artifact_path)

def _list_artifacts():
    request_message = _get_request_message(
        ListArtifacts(),
        schema={
            "run_id": [_assert_string, _assert_required],
            "path": [_assert_string],
            "page_token": [_assert_string],
        },
    )
    response_message = ListArtifacts.Response()
    if request_message.HasField("path"):
        path = request_message.path
    else:
        path = None
    run_id = request_message.run_id or request_message.run_uuid
    run = _get_tracking_store().get_run(run_id)

    if _is_servable_proxied_run_artifact_root(run.info.artifact_uri):
        artifact_entities = _list_artifacts_for_proxied_run_artifact_root(
            proxied_artifact_root=run.info.artifact_uri,
            relative_path=path,
        )
    else:
        artifact_entities = _get_artifact_repo(run).list_artifacts(path)

    response_message.files.extend([a.to_proto() for a in artifact_entities])
    response_message.root_uri = run.info.artifact_uri
    response = Response(mimetype="application/json")
    response.set_data(message_to_json(response_message))
    return response

def get_model_version_artifact_handler():
    from querystring_parser import parser

    query_string = request.query_string.decode("utf-8")
    request_dict = parser.parse(query_string, normalized=True)
    name = request_dict.get("name")
    version = request_dict.get("version")
    artifact_uri = _get_model_registry_store().get_model_version_download_uri(name, version)

    if _is_servable_proxied_run_artifact_root(artifact_uri):
        artifact_repo = _get_artifact_repo_mlflow_artifacts()
        artifact_path = _get_proxied_run_artifact_destination_path(
            proxied_artifact_root=artifact_uri,
            relative_path=request_dict["path"],
        )
    else:
        artifact_repo = get_artifact_repository(artifact_uri)
        artifact_path = request_dict["path"]

    return _send_artifact(artifact_repo, artifact_path)

def get_artifact_handler():
    from querystring_parser import parser

    query_string = request.query_string.decode("utf-8")
    request_dict = parser.parse(query_string, normalized=True)
    run_id = request_dict.get("run_id") or request_dict.get("run_uuid")
    path = request_dict["path"]
    validate_path_is_safe(path)
    run = _get_tracking_store().get_run(run_id)

    if _is_servable_proxied_run_artifact_root(run.info.artifact_uri):
        artifact_repo = _get_artifact_repo_mlflow_artifacts()
        artifact_path = _get_proxied_run_artifact_destination_path(
            proxied_artifact_root=run.info.artifact_uri,
            relative_path=path,
        )
    else:
        artifact_repo = _get_artifact_repo(run)
        artifact_path = path

    return _send_artifact(artifact_repo, artifact_path)

def _list_artifacts():
    request_message = _get_request_message(
        ListArtifacts(),
        schema={
            "run_id": [_assert_string, _assert_required],
            "path": [_assert_string],
            "page_token": [_assert_string],
        },
    )
    response_message = ListArtifacts.Response()
    if request_message.HasField("path"):
        path = request_message.path
        validate_path_is_safe(path)
    else:
        path = None
    run_id = request_message.run_id or request_message.run_uuid
    run = _get_tracking_store().get_run(run_id)

    if _is_servable_proxied_run_artifact_root(run.info.artifact_uri):
        artifact_entities = _list_artifacts_for_proxied_run_artifact_root(
            proxied_artifact_root=run.info.artifact_uri,
            relative_path=path,
        )
    else:
        artifact_entities = _get_artifact_repo(run).list_artifacts(path)

    response_message.files.extend([a.to_proto() for a in artifact_entities])
    response_message.root_uri = run.info.artifact_uri
    response = Response(mimetype="application/json")
    response.set_data(message_to_json(response_message))
    return response

def get_model_version_artifact_handler():
    from querystring_parser import parser

    query_string = request.query_string.decode("utf-8")
    request_dict = parser.parse(query_string, normalized=True)
    name = request_dict.get("name")
    version = request_dict.get("version")
    path = request_dict["path"]
    validate_path_is_safe(path)
    artifact_uri = _get_model_registry_store().get_model_version_download_uri(name, version)
    if _is_servable_proxied_run_artifact_root(artifact_uri):
        artifact_repo = _get_artifact_repo_mlflow_artifacts()
        artifact_path = _get_proxied_run_artifact_destination_path(
            proxied_artifact_root=artifact_uri,
            relative_path=path,
        )
    else:
        artifact_repo = get_artifact_repository(artifact_uri)
        artifact_path = path

    return _send_artifact(artifact_repo, artifact_path)

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. The implementation is based on
    `werkzeug.security.safe_join` (https://github.com/pallets/werkzeug/blob/a3005e6acda7246fe0a684c71921bf4882b4ba1c/src/werkzeug/security.py#L110).
    """
    if path != "":
        path = posixpath.normpath(path)
    if (
        any(sep in path for sep in _os_alt_seps)
        or os.path.isabs(path)
        or path == ".."
        or path.startswith("../")
    ):
        raise MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)

--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -4,16 +4,18 @@
     query_string = request.query_string.decode("utf-8")
     request_dict = parser.parse(query_string, normalized=True)
     run_id = request_dict.get("run_id") or request_dict.get("run_uuid")
+    path = request_dict["path"]
+    validate_path_is_safe(path)
     run = _get_tracking_store().get_run(run_id)

     if _is_servable_proxied_run_artifact_root(run.info.artifact_uri):
         artifact_repo = _get_artifact_repo_mlflow_artifacts()
         artifact_path = _get_proxied_run_artifact_destination_path(
             proxied_artifact_root=run.info.artifact_uri,
-            relative_path=request_dict["path"],
+            relative_path=path,
         )
     else:
         artifact_repo = _get_artifact_repo(run)
-        artifact_path = request_dict["path"]
+        artifact_path = path

     return _send_artifact(artifact_repo, artifact_path)

--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -10,6 +10,7 @@
     response_message = ListArtifacts.Response()
     if request_message.HasField("path"):
         path = request_message.path
+        validate_path_is_safe(path)
     else:
         path = None
     run_id = request_message.run_id or request_message.run_uuid

--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
@@ -5,16 +5,17 @@
     request_dict = parser.parse(query_string, normalized=True)
     name = request_dict.get("name")
     version = request_dict.get("version")
+    path = request_dict["path"]
+    validate_path_is_safe(path)
     artifact_uri = _get_model_registry_store().get_model_version_download_uri(name, version)
-
     if _is_servable_proxied_run_artifact_root(artifact_uri):
         artifact_repo = _get_artifact_repo_mlflow_artifacts()
         artifact_path = _get_proxied_run_artifact_destination_path(
             proxied_artifact_root=artifact_uri,
-            relative_path=request_dict["path"],
+            relative_path=path,
         )
     else:
         artifact_repo = get_artifact_repository(artifact_uri)
-        artifact_path = request_dict["path"]
+        artifact_path = path

     return _send_artifact(artifact_repo, artifact_path)

--- /dev/null
+++ b/mlflow/server/handlers.py
@@ -0,0 +1,15 @@
+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. The implementation is based on
+    `werkzeug.security.safe_join` (https://github.com/pallets/werkzeug/blob/a3005e6acda7246fe0a684c71921bf4882b4ba1c/src/werkzeug/security.py#L110).
+    """
+    if path != "":
+        path = posixpath.normpath(path)
+    if (
+        any(sep in path for sep in _os_alt_seps)
+        or os.path.isabs(path)
+        or path == ".."
+        or path.startswith("../")
+    ):
+        raise MlflowException(f"Invalid path: {path}", error_code=INVALID_PARAMETER_VALUE)

--- /dev/null
+++ b/mlflow/server/handlers.py
@@ -0,0 +1 @@
+_os_alt_seps = list(sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/")

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2023-30172:latest
# bash /workspace/fix-run.sh
set -e
export PATH="/workspace/PoC_env/CVE-2023-30172/bin:$PATH"
cd /workspace/mlflow
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2023-30172/bin/python -m pytest tests/tracking/test_rest_tracking.py::test_path_validation tests/tracking/test_rest_tracking.py::test_validate_path_is_safe_good tests/tracking/test_rest_tracking.py::test_validate_path_is_safe_bad

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2023-30172:latest
# bash /workspace/unit_test.sh
set -e
export PATH="/workspace/PoC_env/CVE-2023-30172/bin:$PATH"
cd /workspace/mlflow
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2023-30172/bin/python -m pytest tests/tracking/test_rest_tracking.py -k "not test_log_model[file] and not test_log_model[sqlalchemy] and not test_artifacts[file] and not test_artifacts[sqlalchemy]" -p no:warning --disable-warnings


Open Forms is an application for creating and publishing smart forms. Prior to versions 1.0.9 and 1.1.1, the cookie consent page in Open Forms contains an open redirect by injecting a `referer` querystring parameter and failing to validate the value. A malicious actor is able to redirect users to a website under their control, opening them up for phishing attacks. The redirect is initiated by the open forms backend which is a legimate page, making it less obvious to end users they are being redirected to a malicious website. Versions 1.0.9 and 1.1.1 contain patches for this issue. There are no known workarounds avaialble.

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 allow_redirect_url(url: str) -> bool:
    """
    Check that a redirect target is allowed against the CORS policy.

    The "Cross-Origin Resource Sharing" configuration specifies which external hosts
    are allowed to access Open Forms. We leverage this configuration to block or allow
    redirects to external hosts.
    """
    cors = CorsMiddleware()
    origin = origin_from_url(url)
    parts = urlparse(url)

    if not cors_conf.CORS_ALLOW_ALL_ORIGINS and not cors.origin_found_in_white_lists(
        origin, parts
    ):
        return False
    else:
        return True

def allow_redirect_url(url: str) -> bool:
    """
    Check that a redirect target is allowed against the CORS policy.

    The "Cross-Origin Resource Sharing" configuration specifies which external hosts
    are allowed to access Open Forms. We leverage this configuration to block or allow
    redirects to external hosts.
    """
    # first, check if the URL is in ALLOWED_HOSTS. We deliberately exclude the wildcard
    # setting to require explicit configuration either via ALLOWED_HOSTS or CORS_* settings.
    allowed_hosts_check = url_has_allowed_host_and_scheme(
        url=url,
        allowed_hosts=[host for host in settings.ALLOWED_HOSTS if host != "*"],
        # settings.ALLOWED_HOSTS means we are serving the domain, so we can enforce our
        # own custom HTTPS setting.
        require_https=settings.IS_HTTPS,
    )
    # if we pass via ALLOWED_HOSTS, short-circuit, otherwise we check the CORS policy
    # for allowed external domains.
    if allowed_hosts_check:
        return True

    cors = CorsMiddleware()
    origin = origin_from_url(url)
    parts = urlparse(url)

    if not cors_conf.CORS_ALLOW_ALL_ORIGINS and not cors.origin_found_in_white_lists(
        origin, parts
    ):
        return False
    else:
        return True

def get_allowed_redirect_url(*candidates: str) -> str:
    """
    Output the first variable passed that is not empty and is an allowed redirect URL.

    Output nothing if none of the values satisfy the requirements.

    Heavily insired on the builtin {% firstof %} tag.
    """
    for candidate in candidates:
        if not candidate:
            continue
        if allow_redirect_url(candidate):
            return candidate
    return ""

--- a/src/openforms/utils/redirect.py
+++ b/src/openforms/utils/redirect.py
@@ -6,6 +6,20 @@
     are allowed to access Open Forms. We leverage this configuration to block or allow
     redirects to external hosts.
     """
+    # first, check if the URL is in ALLOWED_HOSTS. We deliberately exclude the wildcard
+    # setting to require explicit configuration either via ALLOWED_HOSTS or CORS_* settings.
+    allowed_hosts_check = url_has_allowed_host_and_scheme(
+        url=url,
+        allowed_hosts=[host for host in settings.ALLOWED_HOSTS if host != "*"],
+        # settings.ALLOWED_HOSTS means we are serving the domain, so we can enforce our
+        # own custom HTTPS setting.
+        require_https=settings.IS_HTTPS,
+    )
+    # if we pass via ALLOWED_HOSTS, short-circuit, otherwise we check the CORS policy
+    # for allowed external domains.
+    if allowed_hosts_check:
+        return True
+
     cors = CorsMiddleware()
     origin = origin_from_url(url)
     parts = urlparse(url)

--- /dev/null
+++ b/src/openforms/utils/redirect.py
@@ -0,0 +1,14 @@
+def get_allowed_redirect_url(*candidates: str) -> str:
+    """
+    Output the first variable passed that is not empty and is an allowed redirect URL.
+
+    Output nothing if none of the values satisfy the requirements.
+
+    Heavily insired on the builtin {% firstof %} tag.
+    """
+    for candidate in candidates:
+        if not candidate:
+            continue
+        if allow_redirect_url(candidate):
+            return candidate
+    return ""


Insufficient Session Expiration in GitHub repository pyload/pyload prior to 0.5.0b3.dev36.

Insufficient Session Expiration

According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."

def is_authenticated(session=flask.session):
    return session.get("name") and session.get(
        "authenticated"
    )  # NOTE: why checks name?

def is_authenticated(session=flask.session):
    api = flask.current_app.config["PYLOAD_API"]
    user = session.get("name")
    authenticated = session.get("authenticated")

    return authenticated and api.user_exists(user)

    def user_exists(self, username):
        """
        Check if a user actually exists in the database.

        :param username:
        :return: boolean
        """
        return self.pyload.db.user_exists(username)

    @style.queue
    def user_exists(self, user):
        self.c.execute("SELECT name FROM users WHERE name=?", (user,))
        return self.c.fetchone() is not None

--- a/src/pyload/webui/app/helpers.py
+++ b/src/pyload/webui/app/helpers.py
@@ -1,4 +1,6 @@
 def is_authenticated(session=flask.session):
-    return session.get("name") and session.get(
-        "authenticated"
-    )  # NOTE: why checks name?
+    api = flask.current_app.config["PYLOAD_API"]
+    user = session.get("name")
+    authenticated = session.get("authenticated")
+
+    return authenticated and api.user_exists(user)

--- /dev/null
+++ b/src/pyload/webui/app/helpers.py
@@ -0,0 +1,8 @@
+    def user_exists(self, username):
+        """
+        Check if a user actually exists in the database.
+
+        :param username:
+        :return: boolean
+        """
+        return self.pyload.db.user_exists(username)

--- /dev/null
+++ b/src/pyload/webui/app/helpers.py
@@ -0,0 +1,4 @@
+    @style.queue
+    def user_exists(self, user):
+        self.c.execute("SELECT name FROM users WHERE name=?", (user,))
+        return self.c.fetchone() is not None


An open redirect vulnerability exists in Okta OIDC Middleware prior to version 5.0.0 allowing an attacker to redirect a user to an arbitrary URL.

oidcUtil.ensureAuthenticated = (context, options = {}) => {
  return (req, res, next) => {
    const isAuthenticated = req.isAuthenticated && req.isAuthenticated();
    if (isAuthenticated) {
      return next();
    }
    const negotiator = new Negotiator(req);
    if (negotiator.mediaType() === 'text/html') {
      if (!isAuthenticated) {
        if (req.session) {
          req.session.returnTo = req.originalUrl || req.url;
        }
        let url = options.redirectTo;
        if (!url) {
          const loginPath = context.options.routes.login.path;
          url = (new URL(loginPath[0] === '/' ? `.${loginPath}` : `/${loginPath}`, context.options.appBaseUrl + '/')).href;
        }
        return res.redirect(appendOptionsToQuery(url, options));
      }

      next();
    } else {
      res.sendStatus(401);
    }
  };
};

oidcUtil.ensureAuthenticated = (context, options = {}) => {
  return (req, res, next) => {
    const isAuthenticated = req.isAuthenticated && req.isAuthenticated();
    if (isAuthenticated) {
      return next();
    }
    const negotiator = new Negotiator(req);
    if (negotiator.mediaType() === 'text/html') {
      if (!isAuthenticated) {
        if (req.session) {
          // collapse any leading slashes to a single slash to prevent open redirects (OKTA-499372)
          req.session.returnTo = (req.originalUrl || req.url).replace(/^\/+/, '/');
        }
        let url = options.redirectTo;
        if (!url) {
          const loginPath = context.options.routes.login.path;
          url = (new URL(loginPath[0] === '/' ? `.${loginPath}` : `/${loginPath}`, context.options.appBaseUrl + '/')).href;
        }
        return res.redirect(appendOptionsToQuery(url, options));
      }

      next();
    } else {
      res.sendStatus(401);
    }
  };
};

--- a/src/oidcUtil.js
+++ b/src/oidcUtil.js
@@ -8,7 +8,8 @@
     if (negotiator.mediaType() === 'text/html') {
       if (!isAuthenticated) {
         if (req.session) {
-          req.session.returnTo = req.originalUrl || req.url;
+          // collapse any leading slashes to a single slash to prevent open redirects (OKTA-499372)
+          req.session.returnTo = (req.originalUrl || req.url).replace(/^\/+/, '/');
         }
         let url = options.redirectTo;
         if (!url) {


The Mock software contains a vulnerability wherein an attacker could potentially exploit privilege escalation, enabling the execution of arbitrary code with root user privileges. This weakness stems from the absence of proper sandboxing during the expansion and execution of Jinja2 templates, which may be included in certain configuration parameters. While the Mock documentation advises treating users added to the mock group as privileged, certain build systems invoking mock on behalf of users might inadvertently permit less privileged users to define configuration tags. These tags could then be passed as parameters to mock during execution, potentially leading to the utilization of Jinja2 templates for remote privilege escalation and the execution of arbitrary code as the root user on the build server.

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.

    def __init__(self, *args, alias_spec=None, **kwargs):
        '''
        Use the object dict.

        Optional parameter 'alias_spec' is dictionary of form:
        {'aliased_to': ['alias_one', 'alias_two', ...], ...}
        When specified, and one of the aliases is accessed - the
        'aliased_to' config option is returned.
        '''
        self.__dict__.update(*args, **kwargs)

        self._aliases = {}
        if alias_spec:
            for aliased_to, aliases in alias_spec.items():
                for alias in aliases:
                    self._aliases[alias] = aliased_to

    def __render_string(self, value):
        orig = last = value
        max_recursion = self.__dict__.get('jinja_max_recursion', 5)
        for _ in range(max_recursion):
            template = jinja2.Template(value, keep_trailing_newline=True)
            value = _to_native(template.render(self.__dict__))
            if value == last:
                return value
            last = value
        raise ValueError("too deep jinja re-evaluation on '{}'".format(orig))

    def __init__(self, *args, alias_spec=None, **kwargs):
        '''
        Use the object dict.

        Optional parameter 'alias_spec' is dictionary of form:
        {'aliased_to': ['alias_one', 'alias_two', ...], ...}
        When specified, and one of the aliases is accessed - the
        'aliased_to' config option is returned.
        '''
        self.__dict__.update(*args, **kwargs)

        self.sandbox = sandbox.SandboxedEnvironment(keep_trailing_newline=True)

        self._aliases = {}
        if alias_spec:
            for aliased_to, aliases in alias_spec.items():
                for alias in aliases:
                    self._aliases[alias] = aliased_to

    def __render_string(self, value):
        orig = last = value
        max_recursion = self.__dict__.get('jinja_max_recursion', 5)
        for _ in range(max_recursion):
            value = _to_native(self.sandbox.from_string(value).render(self.__dict__, func=lambda:None))
            if value == last:
                return value
            last = value
        raise ValueError("too deep jinja re-evaluation on '{}'".format(orig))

--- a/templated_dictionary/__init__.py
+++ b/templated_dictionary/__init__.py
@@ -9,6 +9,8 @@
         '''
         self.__dict__.update(*args, **kwargs)

+        self.sandbox = sandbox.SandboxedEnvironment(keep_trailing_newline=True)
+
         self._aliases = {}
         if alias_spec:
             for aliased_to, aliases in alias_spec.items():

--- a/templated_dictionary/__init__.py
+++ b/templated_dictionary/__init__.py
@@ -2,8 +2,7 @@
         orig = last = value
         max_recursion = self.__dict__.get('jinja_max_recursion', 5)
         for _ in range(max_recursion):
-            template = jinja2.Template(value, keep_trailing_newline=True)
-            value = _to_native(template.render(self.__dict__))
+            value = _to_native(self.sandbox.from_string(value).render(self.__dict__, func=lambda:None))
             if value == last:
                 return value
             last = value


Django 3.1.x before 3.1.13 and 3.2.x before 3.2.5 allows QuerySet.order_by SQL injection if order_by is untrusted input from a client of a web application.

    def add_ordering(self, *ordering):
        """
        Add items from the 'ordering' sequence to the query's "order by"
        clause. These items are either field names (not column names) --
        possibly with a direction prefix ('-' or '?') -- or OrderBy
        expressions.

        If 'ordering' is empty, clear all ordering from the query.
        """
        errors = []
        for item in ordering:
            if isinstance(item, str):
                if '.' in item:
                    warnings.warn(
                        'Passing column raw column aliases to order_by() is '
                        'deprecated. Wrap %r in a RawSQL expression before '
                        'passing it to order_by().' % item,
                        category=RemovedInDjango40Warning,
                        stacklevel=3,
                    )
                    continue
                if item == '?':
                    continue
                if item.startswith('-'):
                    item = item[1:]
                if item in self.annotations:
                    continue
                if self.extra and item in self.extra:
                    continue
                # names_to_path() validates the lookup. A descriptive
                # FieldError will be raise if it's not.
                self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
            elif not hasattr(item, 'resolve_expression'):
                errors.append(item)
            if getattr(item, 'contains_aggregate', False):
                raise FieldError(
                    'Using an aggregate in order_by() without also including '
                    'it in annotate() is not allowed: %s' % item
                )
        if errors:
            raise FieldError('Invalid order_by arguments: %s' % errors)
        if ordering:
            self.order_by += ordering
        else:
            self.default_ordering = False

    def add_ordering(self, *ordering):
        """
        Add items from the 'ordering' sequence to the query's "order by"
        clause. These items are either field names (not column names) --
        possibly with a direction prefix ('-' or '?') -- or OrderBy
        expressions.

        If 'ordering' is empty, clear all ordering from the query.
        """
        errors = []
        for item in ordering:
            if isinstance(item, str):
                if '.' in item and ORDER_PATTERN.match(item):
                    warnings.warn(
                        'Passing column raw column aliases to order_by() is '
                        'deprecated. Wrap %r in a RawSQL expression before '
                        'passing it to order_by().' % item,
                        category=RemovedInDjango40Warning,
                        stacklevel=3,
                    )
                    continue
                if item == '?':
                    continue
                if item.startswith('-'):
                    item = item[1:]
                if item in self.annotations:
                    continue
                if self.extra and item in self.extra:
                    continue
                # names_to_path() validates the lookup. A descriptive
                # FieldError will be raise if it's not.
                self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
            elif not hasattr(item, 'resolve_expression'):
                errors.append(item)
            if getattr(item, 'contains_aggregate', False):
                raise FieldError(
                    'Using an aggregate in order_by() without also including '
                    'it in annotate() is not allowed: %s' % item
                )
        if errors:
            raise FieldError('Invalid order_by arguments: %s' % errors)
        if ordering:
            self.order_by += ordering
        else:
            self.default_ordering = False

--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -10,7 +10,7 @@
         errors = []
         for item in ordering:
             if isinstance(item, str):
-                if '.' in item:
+                if '.' in item and ORDER_PATTERN.match(item):
                     warnings.warn(
                         'Passing column raw column aliases to order_by() is '
                         'deprecated. Wrap %r in a RawSQL expression before '

--- /dev/null
+++ b/django/db/models/sql/query.py
@@ -0,0 +1 @@
+ORDER_PATTERN = _lazy_re_compile(r'[-+]?[.\w]+$')

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

cd /workspace/django
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
cd tests && /workspace/PoC_env/CVE-2021-35042/bin/python ./runtests.py queries.tests.QuerySetExceptionTests.test_order_by_escape_prevention

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

cd /workspace/django
git apply --whitespace=nowarn /workspace/fix.patch
cd tests && /workspace/PoC_env/CVE-2021-35042/bin/python ./runtests.py queries.tests


codecov-node npm module before 3.6.5 allows remote attackers to execute arbitrary commands.The value provided as part of the gcov-root argument is executed by the exec function within lib/codecov.js. This vulnerability exists due to an incomplete fix of CVE-2020-7596.

var upload = function(args, on_success, on_failure) {
  // Build query
  var codecov_endpoint =
    args.options.url ||
    process.env.codecov_url ||
    process.env.CODECOV_URL ||
    'https://codecov.io'
  var query = {}
  var debug = []
  var yamlFile =
    args.options.yml ||
    process.env.codecov_yml ||
    process.env.CODECOV_YML ||
    'codecov.yml'

  console.log(
    '' +
      '  _____          _  \n' +
      ' / ____|        | |  \n' +
      '| |     ___   __| | ___  ___ _____   __  \n' +
      '| |    / _ \\ / _` |/ _ \\/ __/ _ \\ \\ / /  \n' +
      '| |___| (_) | (_| |  __/ (_| (_) \\ V /  \n' +
      ' \\_____\\___/ \\__,_|\\___|\\___\\___/ \\_/  \n' +
      '                                ' +
      version
  )

  if ((args.options.disable || '').split(',').indexOf('detect') === -1) {
    console.log('==> Detecting CI Provider')
    query = detectProvider()
  } else {
    debug.push('disabled detect')
  }

  query.yaml = [yamlFile, '.codecov.yml'].reduce(function(result, file) {
    return (
      result ||
      (fs.existsSync(path.resolve(process.cwd(), file))
        ? path.resolve(process.cwd(), file)
        : undefined)
    )
  }, undefined)

  if (args.options.build) {
    query.build = args.options.build
  }

  if (args.options.commit) {
    query.commit = args.options.commit
  }

  if (args.options.branch) {
    query.branch = args.options.branch
  }

  if (args.options.slug) {
    query.slug = args.options.slug
  }

  var flags =
    args.options.flags || process.env.codecov_flags || process.env.CODECOV_FLAGS
  if (flags) {
    query.flags = flags
  }

  var yamlToken
  try {
    var loadedYamlFile = jsYaml.safeLoad(fs.readFileSync(query.yaml, 'utf8'))
    yamlToken =
      loadedYamlFile && loadedYamlFile.codecov && loadedYamlFile.codecov.token
  } catch (e) {
    // silently fail
  }
  var token =
    args.options.token ||
    yamlToken ||
    process.env.codecov_token ||
    process.env.CODECOV_TOKEN
  if (token) {
    query.token = token
  }

  query.package = 'node-' + version

  console.log('==> Configuration: ')
  console.log('    Endpoint: ' + codecov_endpoint)
  // Don't output `query` directly as it contains the upload token
  console.log({
    commit: query.commit,
    branch: query.branch,
    package: query.package,
  })

  var upload = ''

  // Add specified env vars
  var env_found = false
  if (args.options.env || process.env.CODECOV_ENV || process.env.codecov_env) {
    var env = (
      args.options.env +
      ',' +
      (process.env.CODECOV_ENV || '') +
      ',' +
      (process.env.codecov_env || '')
    ).split(',')
    for (var i = env.length - 1; i >= 0; i--) {
      if (env[i]) {
        upload += env[i] + '=' + (process.env[env[i]] || '').toString() + '\n'
        env_found = true
      }
    }
    if (env_found) {
      upload += '<<<<<< ENV\n'
    }
  }

  // List git files
  var root = path.resolve(args.options.root || query.root || '.')
  console.log('==> Building file structure')
  try {
    upload +=
      execSync('git ls-files || hg locate', { cwd: root })
        .toString()
        .trim() + '\n<<<<<< network\n'
  } catch (err) {
    // not a git/hg dir, emulating git/hg ignore behavior
    upload +=
      walk
        .sync({ path: root, ignoreFiles: ['.gitignore', '.hgignore'] })
        .join('\n')
        .trim() + '\n<<<<<< network\n'
  }
  // Make gcov reports
  if ((args.options.disable || '').split(',').indexOf('gcov') === -1) {
    try {
      console.log('==> Generating gcov reports (skip via --disable=gcov)')
      var gcg = args.options['gcov-glob'] || ''
      if (gcg) {
        if (!isWindows) {
          gcg = gcg
            .split(' ')
            .map(function(p) {
              return "-not -path '" + p + "'"
            })
            .join(' ')
        } else {
          gcg = gcg
            .split(' ')
            .map(function(p) {
              return '^| findstr /i /v ' + p
            })
            .join(' ')
        }
      }
      var gcov
      if (!isWindows) {
        gcov =
          'find ' +
          (args.options['gcov-root'] || root) +
          " -type f -name '*.gcno' " +
          gcg +
          ' -exec ' +
          (validator.escape(args.options['gcov-exec']) || 'gcov') +
          ' ' +
          (validator.escape(args.options['gcov-args']) || '') +
          ' {} +'
      } else {
        // @TODO support for root
        // not straight forward due to nature of windows command dir
        gcov =
          'for /f "delims=" %g in (\'dir /a-d /b /s *.gcno ' +
          gcg +
          "') do " +
          (args.options['gcov-exec'] || 'gcov') +
          ' ' +
          (args.options['gcov-args'] || '') +
          ' %g'
      }
      debug.push(gcov)
      console.log('    $ ' + gcov)
      execSync(gcov)
    } catch (e) {
      console.log('    Failed to run gcov command.')
    }
  } else {
    debug.push('disabled gcov')
  }

  // Detect .bowerrc
  var bowerrc
  if (!isWindows) {
    bowerrc = execSync('test -f .bowerrc && cat .bowerrc || echo ""', {
      cwd: root,
    })
      .toString()
      .trim()
  } else {
    bowerrc = execSync('if exist .bowerrc type .bowerrc', { cwd: root })
      .toString()
      .trim()
  }
  if (bowerrc) {
    bowerrc = JSON.parse(bowerrc).directory
    if (bowerrc) {
      if (!isWindows) {
        more_patterns =
          " -not -path '*/" + bowerrc.toString().replace(/\/$/, '') + "/*'"
      } else {
        more_patterns =
          '| findstr /i /v \\' + bowerrc.toString().replace(/\/$/, '') + '\\'
      }
    }
  }

  var files = [],
    file = null
  if (args.options.pipe) {
    // Append piped reports
    upload += '# path=piped\n' + args.options.pipe.join('') + '\n<<<<<< EOF\n'
    console.log('==> Reading report from stdin')
  } else if (args.options.file) {
    // Append manually entered reports
    file = args.options.file
    console.log('==> Targeting specific file')
    try {
      upload +=
        '# path=' +
        file +
        '\n' +
        fs.readFileSync(file, 'utf8').toString() +
        '\n<<<<<< EOF\n'
      console.log('    + ' + file)
      files.push(file)
    } catch (e) {
      debug.push('failed: ' + file.split('/').pop())
      console.log('    X Failed to read file at ' + file)
    }
  } else if ((args.options.disable || '').split(',').indexOf('search') === -1) {
    console.log('==> Scanning for reports')
    var _files
    if (!isWindows) {
      _files = execSync('find ' + root + ' ' + patterns + more_patterns)
        .toString()
        .trim()
        .split('\n')
    } else {
      // @TODO support for a root directory
      // It's not straightforward due to the nature of the dir command
      _files = execSync('dir ' + patterns + more_patterns)
        .toString()
        .trim()
        .split('\r\n')
    }
    if (_files) {
      for (var i2 = _files.length - 1; i2 >= 0; i2--) {
        file = _files[i2]
        try {
          upload +=
            '# path=' +
            file +
            '\n' +
            fs.readFileSync(file, 'utf8').toString() +
            '\n<<<<<< EOF\n'
          console.log('    + ' + file)
          files.push(file)
        } catch (e) {
          debug.push('failed: ' + file.split('/').pop())
          console.log('    X Failed to read file at ' + file)
        }
      }
    }
  } else {
    debug.push('disabled search')
  }

  if (files) {
    // Upload to Codecov
    if (args.options.dump) {
      console.log('-------- DEBUG START --------')
      console.log(upload)
      console.log('-------- DEBUG END --------')
    } else {
      console.log('==> Uploading reports')
      var _upload
      if ((args.options.disable || '').split(',').indexOf('s3') === -1) {
        _upload = sendToCodecovV3
      } else {
        _upload = sendToCodecovV2
      }
      _upload(
        codecov_endpoint,
        query,
        upload,
        function() {
          // remove files after Uploading
          if (args.options.clear) {
            for (var i = files.length - 1; i >= 0; i--) {
              try {
                fs.unlinkSync(files[i])
              } catch (e) {}
            }
          }
          if (on_success) {
            on_success.apply(this, arguments)
          }
        },
        on_failure || function() {}
      )
    }
  }

  return {
    body: upload,
    files: files,
    query: query,
    debug: debug,
    url: codecov_endpoint,
  }
}

var upload = function(args, on_success, on_failure) {
  // Build query
  var codecov_endpoint =
    args.options.url ||
    process.env.codecov_url ||
    process.env.CODECOV_URL ||
    'https://codecov.io'
  var query = {}
  var debug = []
  var yamlFile =
    args.options.yml ||
    process.env.codecov_yml ||
    process.env.CODECOV_YML ||
    'codecov.yml'

  console.log(
    '' +
      '  _____          _  \n' +
      ' / ____|        | |  \n' +
      '| |     ___   __| | ___  ___ _____   __  \n' +
      '| |    / _ \\ / _` |/ _ \\/ __/ _ \\ \\ / /  \n' +
      '| |___| (_) | (_| |  __/ (_| (_) \\ V /  \n' +
      ' \\_____\\___/ \\__,_|\\___|\\___\\___/ \\_/  \n' +
      '                                ' +
      version
  )

  if ((args.options.disable || '').split(',').indexOf('detect') === -1) {
    console.log('==> Detecting CI Provider')
    query = detectProvider()
  } else {
    debug.push('disabled detect')
  }

  query.yaml = [yamlFile, '.codecov.yml'].reduce(function(result, file) {
    return (
      result ||
      (fs.existsSync(path.resolve(process.cwd(), file))
        ? path.resolve(process.cwd(), file)
        : undefined)
    )
  }, undefined)

  if (args.options.build) {
    query.build = args.options.build
  }

  if (args.options.commit) {
    query.commit = args.options.commit
  }

  if (args.options.branch) {
    query.branch = args.options.branch
  }

  if (args.options.slug) {
    query.slug = args.options.slug
  }

  var flags =
    args.options.flags || process.env.codecov_flags || process.env.CODECOV_FLAGS
  if (flags) {
    query.flags = flags
  }

  var yamlToken
  try {
    var loadedYamlFile = jsYaml.safeLoad(fs.readFileSync(query.yaml, 'utf8'))
    yamlToken =
      loadedYamlFile && loadedYamlFile.codecov && loadedYamlFile.codecov.token
  } catch (e) {
    // silently fail
  }
  var token =
    args.options.token ||
    yamlToken ||
    process.env.codecov_token ||
    process.env.CODECOV_TOKEN
  if (token) {
    query.token = token
  }

  query.package = 'node-' + version

  console.log('==> Configuration: ')
  console.log('    Endpoint: ' + codecov_endpoint)
  // Don't output `query` directly as it contains the upload token
  console.log({
    commit: query.commit,
    branch: query.branch,
    package: query.package,
  })

  var upload = ''

  // Add specified env vars
  var env_found = false
  if (args.options.env || process.env.CODECOV_ENV || process.env.codecov_env) {
    var env = (
      args.options.env +
      ',' +
      (process.env.CODECOV_ENV || '') +
      ',' +
      (process.env.codecov_env || '')
    ).split(',')
    for (var i = env.length - 1; i >= 0; i--) {
      if (env[i]) {
        upload += env[i] + '=' + (process.env[env[i]] || '').toString() + '\n'
        env_found = true
      }
    }
    if (env_found) {
      upload += '<<<<<< ENV\n'
    }
  }

  // List git files
  var root = path.resolve(args.options.root || query.root || '.')
  console.log('==> Building file structure')
  try {
    upload +=
      execSync('git ls-files || hg locate', { cwd: root })
        .toString()
        .trim() + '\n<<<<<< network\n'
  } catch (err) {
    // not a git/hg dir, emulating git/hg ignore behavior
    upload +=
      walk
        .sync({ path: root, ignoreFiles: ['.gitignore', '.hgignore'] })
        .join('\n')
        .trim() + '\n<<<<<< network\n'
  }
  // Make gcov reports
  if ((args.options.disable || '').split(',').indexOf('gcov') === -1) {
    try {
      console.log('==> Generating gcov reports (skip via --disable=gcov)')
      var gcg = args.options['gcov-glob'] || ''
      if (gcg) {
        if (!isWindows) {
          gcg = gcg
            .split(' ')
            .map(function(p) {
              return "-not -path '" + p + "'"
            })
            .join(' ')
        } else {
          gcg = gcg
            .split(' ')
            .map(function(p) {
              return '^| findstr /i /v ' + p
            })
            .join(' ')
        }
      }
      var gcov
      if (!isWindows) {
        gcov =
          'find ' +
          (sanitizeVar(args.options['gcov-root']) || root) +
          " -type f -name '*.gcno' " +
          gcg +
          ' -exec ' +
          (sanitizeVar(args.options['gcov-exec']) || 'gcov') +
          ' ' +
          (sanitizeVar(args.options['gcov-args']) || '') +
          ' {} +'
      } else {
        // @TODO support for root
        // not straight forward due to nature of windows command dir
        gcov =
          'for /f "delims=" %g in (\'dir /a-d /b /s *.gcno ' +
          gcg +
          "') do " +
          (sanitizeVar(args.options['gcov-exec']) || 'gcov') +
          ' ' +
          (sanitizeVar(args.options['gcov-args']) || '') +
          ' %g'
      }
      debug.push(gcov)
      console.log('    $ ' + gcov)
      execSync(gcov)
    } catch (e) {
      console.log('    Failed to run gcov command.')
    }
  } else {
    debug.push('disabled gcov')
  }

  // Detect .bowerrc
  var bowerrc
  if (!isWindows) {
    bowerrc = execSync('test -f .bowerrc && cat .bowerrc || echo ""', {
      cwd: root,
    })
      .toString()
      .trim()
  } else {
    bowerrc = execSync('if exist .bowerrc type .bowerrc', { cwd: root })
      .toString()
      .trim()
  }
  if (bowerrc) {
    bowerrc = JSON.parse(bowerrc).directory
    if (bowerrc) {
      if (!isWindows) {
        more_patterns =
          " -not -path '*/" + bowerrc.toString().replace(/\/$/, '') + "/*'"
      } else {
        more_patterns =
          '| findstr /i /v \\' + bowerrc.toString().replace(/\/$/, '') + '\\'
      }
    }
  }

  var files = [],
    file = null
  if (args.options.pipe) {
    // Append piped reports
    upload += '# path=piped\n' + args.options.pipe.join('') + '\n<<<<<< EOF\n'
    console.log('==> Reading report from stdin')
  } else if (args.options.file) {
    // Append manually entered reports
    file = args.options.file
    console.log('==> Targeting specific file')
    try {
      upload +=
        '# path=' +
        file +
        '\n' +
        fs.readFileSync(file, 'utf8').toString() +
        '\n<<<<<< EOF\n'
      console.log('    + ' + file)
      files.push(file)
    } catch (e) {
      debug.push('failed: ' + file.split('/').pop())
      console.log('    X Failed to read file at ' + file)
    }
  } else if ((args.options.disable || '').split(',').indexOf('search') === -1) {
    console.log('==> Scanning for reports')
    var _files
    if (!isWindows) {
      _files = execSync('find ' + root + ' ' + patterns + more_patterns)
        .toString()
        .trim()
        .split('\n')
    } else {
      // @TODO support for a root directory
      // It's not straightforward due to the nature of the dir command
      _files = execSync('dir ' + patterns + more_patterns)
        .toString()
        .trim()
        .split('\r\n')
    }
    if (_files) {
      for (var i2 = _files.length - 1; i2 >= 0; i2--) {
        file = _files[i2]
        try {
          upload +=
            '# path=' +
            file +
            '\n' +
            fs.readFileSync(file, 'utf8').toString() +
            '\n<<<<<< EOF\n'
          console.log('    + ' + file)
          files.push(file)
        } catch (e) {
          debug.push('failed: ' + file.split('/').pop())
          console.log('    X Failed to read file at ' + file)
        }
      }
    }
  } else {
    debug.push('disabled search')
  }

  if (files) {
    // Upload to Codecov
    if (args.options.dump) {
      console.log('-------- DEBUG START --------')
      console.log(upload)
      console.log('-------- DEBUG END --------')
    } else {
      console.log('==> Uploading reports')
      var _upload
      if ((args.options.disable || '').split(',').indexOf('s3') === -1) {
        _upload = sendToCodecovV3
      } else {
        _upload = sendToCodecovV2
      }
      _upload(
        codecov_endpoint,
        query,
        upload,
        function() {
          // remove files after Uploading
          if (args.options.clear) {
            for (var i = files.length - 1; i >= 0; i--) {
              try {
                fs.unlinkSync(files[i])
              } catch (e) {}
            }
          }
          if (on_success) {
            on_success.apply(this, arguments)
          }
        },
        on_failure || function() {}
      )
    }
  }

  return {
    body: upload,
    files: files,
    query: query,
    debug: debug,
    url: codecov_endpoint,
  }
}

--- a/lib/codecov.js
+++ b/lib/codecov.js
@@ -156,13 +156,13 @@
       if (!isWindows) {
         gcov =
           'find ' +
-          (args.options['gcov-root'] || root) +
+          (sanitizeVar(args.options['gcov-root']) || root) +
           " -type f -name '*.gcno' " +
           gcg +
           ' -exec ' +
-          (validator.escape(args.options['gcov-exec']) || 'gcov') +
+          (sanitizeVar(args.options['gcov-exec']) || 'gcov') +
           ' ' +
-          (validator.escape(args.options['gcov-args']) || '') +
+          (sanitizeVar(args.options['gcov-args']) || '') +
           ' {} +'
       } else {
         // @TODO support for root
@@ -171,9 +171,9 @@
           'for /f "delims=" %g in (\'dir /a-d /b /s *.gcno ' +
           gcg +
           "') do " +
-          (args.options['gcov-exec'] || 'gcov') +
+          (sanitizeVar(args.options['gcov-exec']) || 'gcov') +
           ' ' +
-          (args.options['gcov-args'] || '') +
+          (sanitizeVar(args.options['gcov-args']) || '') +
           ' %g'
       }
       debug.push(gcov)