Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to version 4.10.4, for regular (non-LiveQuery) queries, the session token is removed from the response, but for LiveQuery payloads it is currently not. If a user has a LiveQuery subscription on the `Parse.User` class, all session tokens created during user sign-ups will be broadcast as part of the LiveQuery payload. A patch in version 4.10.4 removes session tokens from the LiveQuery payload. As a workaround, set `user.acl(new Parse.ACL())` in a beforeSave trigger to make the user private already on sign-up.

Exposure of Sensitive Information to an Unauthorized Actor

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

  _onAfterDelete(message: any): void {
    logger.verbose(Parse.applicationId + 'afterDelete is triggered');

    let deletedParseObject = message.currentParseObject.toJSON();
    const classLevelPermissions = message.classLevelPermissions;
    const className = deletedParseObject.className;
    logger.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id);
    logger.verbose('Current client number : %d', this.clients.size);

    const classSubscriptions = this.subscriptions.get(className);
    if (typeof classSubscriptions === 'undefined') {
      logger.debug('Can not find subscriptions under this class ' + className);
      return;
    }
    for (const subscription of classSubscriptions.values()) {
      const isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription);
      if (!isSubscriptionMatched) {
        continue;
      }
      for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
        const client = this.clients.get(clientId);
        if (typeof client === 'undefined') {
          continue;
        }
        for (const requestId of requestIds) {
          const acl = message.currentParseObject.getACL();
          // Check CLP
          const op = this._getCLPOperation(subscription.query);
          let res = {};
          this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
            .then(() => {
              // Check ACL
              return this._matchesACL(acl, client, requestId);
            })
            .then(isMatched => {
              if (!isMatched) {
                return null;
              }
              res = {
                event: 'delete',
                sessionToken: client.sessionToken,
                object: deletedParseObject,
                clients: this.clients.size,
                subscriptions: this.subscriptions.size,
                useMasterKey: client.hasMasterKey,
                installationId: client.installationId,
                sendEvent: true,
              };
              return maybeRunAfterEventTrigger('afterEvent', className, res);
            })
            .then(() => {
              if (!res.sendEvent) {
                return;
              }
              if (res.object && typeof res.object.toJSON === 'function') {
                deletedParseObject = res.object.toJSON();
                deletedParseObject.className = className;
              }
              client.pushDelete(requestId, deletedParseObject);
            })
            .catch(error => {
              Client.pushError(
                client.parseWebSocket,
                error.code || 141,
                error.message || error,
                false,
                requestId
              );
              logger.error(
                `Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
                  JSON.stringify(error)
              );
            });
        }
      }
    }
  }

  _onAfterSave(message: any): void {
    logger.verbose(Parse.applicationId + 'afterSave is triggered');

    let originalParseObject = null;
    if (message.originalParseObject) {
      originalParseObject = message.originalParseObject.toJSON();
    }
    const classLevelPermissions = message.classLevelPermissions;
    let currentParseObject = message.currentParseObject.toJSON();
    const className = currentParseObject.className;
    logger.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id);
    logger.verbose('Current client number : %d', this.clients.size);

    const classSubscriptions = this.subscriptions.get(className);
    if (typeof classSubscriptions === 'undefined') {
      logger.debug('Can not find subscriptions under this class ' + className);
      return;
    }
    for (const subscription of classSubscriptions.values()) {
      const isOriginalSubscriptionMatched = this._matchesSubscription(
        originalParseObject,
        subscription
      );
      const isCurrentSubscriptionMatched = this._matchesSubscription(
        currentParseObject,
        subscription
      );
      for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
        const client = this.clients.get(clientId);
        if (typeof client === 'undefined') {
          continue;
        }
        for (const requestId of requestIds) {
          // Set orignal ParseObject ACL checking promise, if the object does not match
          // subscription, we do not need to check ACL
          let originalACLCheckingPromise;
          if (!isOriginalSubscriptionMatched) {
            originalACLCheckingPromise = Promise.resolve(false);
          } else {
            let originalACL;
            if (message.originalParseObject) {
              originalACL = message.originalParseObject.getACL();
            }
            originalACLCheckingPromise = this._matchesACL(originalACL, client, requestId);
          }
          // Set current ParseObject ACL checking promise, if the object does not match
          // subscription, we do not need to check ACL
          let currentACLCheckingPromise;
          let res = {};
          if (!isCurrentSubscriptionMatched) {
            currentACLCheckingPromise = Promise.resolve(false);
          } else {
            const currentACL = message.currentParseObject.getACL();
            currentACLCheckingPromise = this._matchesACL(currentACL, client, requestId);
          }
          const op = this._getCLPOperation(subscription.query);
          this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
            .then(() => {
              return Promise.all([originalACLCheckingPromise, currentACLCheckingPromise]);
            })
            .then(([isOriginalMatched, isCurrentMatched]) => {
              logger.verbose(
                'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s',
                originalParseObject,
                currentParseObject,
                isOriginalSubscriptionMatched,
                isCurrentSubscriptionMatched,
                isOriginalMatched,
                isCurrentMatched,
                subscription.hash
              );
              // Decide event type
              let type;
              if (isOriginalMatched && isCurrentMatched) {
                type = 'update';
              } else if (isOriginalMatched && !isCurrentMatched) {
                type = 'leave';
              } else if (!isOriginalMatched && isCurrentMatched) {
                if (originalParseObject) {
                  type = 'enter';
                } else {
                  type = 'create';
                }
              } else {
                return null;
              }
              message.event = type;
              res = {
                event: type,
                sessionToken: client.sessionToken,
                object: currentParseObject,
                original: originalParseObject,
                clients: this.clients.size,
                subscriptions: this.subscriptions.size,
                useMasterKey: client.hasMasterKey,
                installationId: client.installationId,
                sendEvent: true,
              };
              return maybeRunAfterEventTrigger('afterEvent', className, res);
            })
            .then(
              () => {
                if (!res.sendEvent) {
                  return;
                }
                if (res.object && typeof res.object.toJSON === 'function') {
                  currentParseObject = res.object.toJSON();
                  currentParseObject.className = res.object.className || className;
                }

                if (res.original && typeof res.original.toJSON === 'function') {
                  originalParseObject = res.original.toJSON();
                  originalParseObject.className = res.original.className || className;
                }
                const functionName =
                  'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
                if (client[functionName]) {
                  client[functionName](requestId, currentParseObject, originalParseObject);
                }
              },
              error => {
                Client.pushError(
                  client.parseWebSocket,
                  error.code || 141,
                  error.message || error,
                  false,
                  requestId
                );
                logger.error(
                  `Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
                    JSON.stringify(error)
                );
              }
            );
        }
      }
    }
  }

  _onAfterDelete(message: any): void {
    logger.verbose(Parse.applicationId + 'afterDelete is triggered');

    let deletedParseObject = message.currentParseObject.toJSON();
    const classLevelPermissions = message.classLevelPermissions;
    const className = deletedParseObject.className;
    logger.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id);
    logger.verbose('Current client number : %d', this.clients.size);

    const classSubscriptions = this.subscriptions.get(className);
    if (typeof classSubscriptions === 'undefined') {
      logger.debug('Can not find subscriptions under this class ' + className);
      return;
    }
    for (const subscription of classSubscriptions.values()) {
      const isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription);
      if (!isSubscriptionMatched) {
        continue;
      }
      for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
        const client = this.clients.get(clientId);
        if (typeof client === 'undefined') {
          continue;
        }
        for (const requestId of requestIds) {
          const acl = message.currentParseObject.getACL();
          // Check CLP
          const op = this._getCLPOperation(subscription.query);
          let res = {};
          this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
            .then(() => {
              // Check ACL
              return this._matchesACL(acl, client, requestId);
            })
            .then(isMatched => {
              if (!isMatched) {
                return null;
              }
              res = {
                event: 'delete',
                sessionToken: client.sessionToken,
                object: deletedParseObject,
                clients: this.clients.size,
                subscriptions: this.subscriptions.size,
                useMasterKey: client.hasMasterKey,
                installationId: client.installationId,
                sendEvent: true,
              };
              return maybeRunAfterEventTrigger('afterEvent', className, res);
            })
            .then(() => {
              if (!res.sendEvent) {
                return;
              }
              if (res.object && typeof res.object.toJSON === 'function') {
                deletedParseObject = res.object.toJSON();
                deletedParseObject.className = className;
              }
              if (
                (deletedParseObject.className === '_User' ||
                  deletedParseObject.className === '_Session') &&
                !client.hasMasterKey
              ) {
                delete deletedParseObject.sessionToken;
                delete deletedParseObject.authData;
              }
              client.pushDelete(requestId, deletedParseObject);
            })
            .catch(error => {
              Client.pushError(
                client.parseWebSocket,
                error.code || 141,
                error.message || error,
                false,
                requestId
              );
              logger.error(
                `Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
                  JSON.stringify(error)
              );
            });
        }
      }
    }
  }

  _onAfterSave(message: any): void {
    logger.verbose(Parse.applicationId + 'afterSave is triggered');

    let originalParseObject = null;
    if (message.originalParseObject) {
      originalParseObject = message.originalParseObject.toJSON();
    }
    const classLevelPermissions = message.classLevelPermissions;
    let currentParseObject = message.currentParseObject.toJSON();
    const className = currentParseObject.className;
    logger.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id);
    logger.verbose('Current client number : %d', this.clients.size);

    const classSubscriptions = this.subscriptions.get(className);
    if (typeof classSubscriptions === 'undefined') {
      logger.debug('Can not find subscriptions under this class ' + className);
      return;
    }
    for (const subscription of classSubscriptions.values()) {
      const isOriginalSubscriptionMatched = this._matchesSubscription(
        originalParseObject,
        subscription
      );
      const isCurrentSubscriptionMatched = this._matchesSubscription(
        currentParseObject,
        subscription
      );
      for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
        const client = this.clients.get(clientId);
        if (typeof client === 'undefined') {
          continue;
        }
        for (const requestId of requestIds) {
          // Set orignal ParseObject ACL checking promise, if the object does not match
          // subscription, we do not need to check ACL
          let originalACLCheckingPromise;
          if (!isOriginalSubscriptionMatched) {
            originalACLCheckingPromise = Promise.resolve(false);
          } else {
            let originalACL;
            if (message.originalParseObject) {
              originalACL = message.originalParseObject.getACL();
            }
            originalACLCheckingPromise = this._matchesACL(originalACL, client, requestId);
          }
          // Set current ParseObject ACL checking promise, if the object does not match
          // subscription, we do not need to check ACL
          let currentACLCheckingPromise;
          let res = {};
          if (!isCurrentSubscriptionMatched) {
            currentACLCheckingPromise = Promise.resolve(false);
          } else {
            const currentACL = message.currentParseObject.getACL();
            currentACLCheckingPromise = this._matchesACL(currentACL, client, requestId);
          }
          const op = this._getCLPOperation(subscription.query);
          this._matchesCLP(classLevelPermissions, message.currentParseObject, client, requestId, op)
            .then(() => {
              return Promise.all([originalACLCheckingPromise, currentACLCheckingPromise]);
            })
            .then(([isOriginalMatched, isCurrentMatched]) => {
              logger.verbose(
                'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s',
                originalParseObject,
                currentParseObject,
                isOriginalSubscriptionMatched,
                isCurrentSubscriptionMatched,
                isOriginalMatched,
                isCurrentMatched,
                subscription.hash
              );
              // Decide event type
              let type;
              if (isOriginalMatched && isCurrentMatched) {
                type = 'update';
              } else if (isOriginalMatched && !isCurrentMatched) {
                type = 'leave';
              } else if (!isOriginalMatched && isCurrentMatched) {
                if (originalParseObject) {
                  type = 'enter';
                } else {
                  type = 'create';
                }
              } else {
                return null;
              }
              message.event = type;
              res = {
                event: type,
                sessionToken: client.sessionToken,
                object: currentParseObject,
                original: originalParseObject,
                clients: this.clients.size,
                subscriptions: this.subscriptions.size,
                useMasterKey: client.hasMasterKey,
                installationId: client.installationId,
                sendEvent: true,
              };
              return maybeRunAfterEventTrigger('afterEvent', className, res);
            })
            .then(
              () => {
                if (!res.sendEvent) {
                  return;
                }
                if (res.object && typeof res.object.toJSON === 'function') {
                  currentParseObject = res.object.toJSON();
                  currentParseObject.className = res.object.className || className;
                }

                if (res.original && typeof res.original.toJSON === 'function') {
                  originalParseObject = res.original.toJSON();
                  originalParseObject.className = res.original.className || className;
                }
                if (
                  (currentParseObject.className === '_User' ||
                    currentParseObject.className === '_Session') &&
                  !client.hasMasterKey
                ) {
                  delete currentParseObject.sessionToken;
                  delete originalParseObject?.sessionToken;
                  delete currentParseObject.authData;
                  delete originalParseObject?.authData;
                }
                const functionName =
                  'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
                if (client[functionName]) {
                  client[functionName](requestId, currentParseObject, originalParseObject);
                }
              },
              error => {
                Client.pushError(
                  client.parseWebSocket,
                  error.code || 141,
                  error.message || error,
                  false,
                  requestId
                );
                logger.error(
                  `Failed running afterLiveQueryEvent on class ${className} for event ${res.event} with session ${res.sessionToken} with:\n Error: ` +
                    JSON.stringify(error)
                );
              }
            );
        }
      }
    }
  }

  it('should strip out session token in LiveQuery', async () => {
    await reconfigureServer({
      liveQuery: { classNames: ['_User'] },
      startLiveQueryServer: true,
      verbose: false,
      silent: true,
    });

    const user = new Parse.User();
    user.setUsername('username');
    user.setPassword('password');
    user.set('foo', 'bar');

    const query = new Parse.Query(Parse.User);
    query.equalTo('foo', 'bar');
    const subscription = await query.subscribe();

    const events = ['create', 'update', 'enter', 'leave', 'delete'];
    const response = (obj, prev) => {
      expect(obj.get('sessionToken')).toBeUndefined();
      expect(obj.sessionToken).toBeUndefined();
      expect(prev?.sessionToken).toBeUndefined();
      if (prev && prev.get) {
        expect(prev.get('sessionToken')).toBeUndefined();
      }
    };
    const calls = {};
    for (const key of events) {
      calls[key] = response;
      spyOn(calls, key).and.callThrough();
      subscription.on(key, calls[key]);
    }
    await user.signUp();
    user.unset('foo');
    await user.save();
    user.set('foo', 'bar');
    await user.save();
    user.set('yolo', 'bar');
    await user.save();
    await user.destroy();
    await new Promise(resolve => process.nextTick(resolve));
    for (const key of events) {
      expect(calls[key]).toHaveBeenCalled();
    }
  });

  it('should strip out authdata in LiveQuery', async () => {
    const provider = getMockFacebookProvider();
    Parse.User._registerAuthenticationProvider(provider);

    await reconfigureServer({
      liveQuery: { classNames: ['_User'] },
      startLiveQueryServer: true,
      verbose: false,
      silent: true,
    });

    const query = new Parse.Query(Parse.User);
    query.doesNotExist('foo');
    const subscription = await query.subscribe();

    const events = ['create', 'update', 'enter', 'leave', 'delete'];
    const response = (obj, prev) => {
      expect(obj.get('authData')).toBeUndefined();
      expect(obj.authData).toBeUndefined();
      expect(prev?.authData).toBeUndefined();
      if (prev && prev.get) {
        expect(prev.get('authData')).toBeUndefined();
      }
    };
    const calls = {};
    for (const key of events) {
      calls[key] = response;
      spyOn(calls, key).and.callThrough();
      subscription.on(key, calls[key]);
    }
    const user = await Parse.User._logInWith('facebook');

    user.set('foo', 'bar');
    await user.save();
    user.unset('foo');
    await user.save();
    user.set('yolo', 'bar');
    await user.save();
    await user.destroy();
    await new Promise(resolve => process.nextTick(resolve));
    for (const key of events) {
      expect(calls[key]).toHaveBeenCalled();
    }
  });

--- a/src/LiveQuery/ParseLiveQueryServer.js
+++ b/src/LiveQuery/ParseLiveQueryServer.js
@@ -56,6 +56,14 @@
                 deletedParseObject = res.object.toJSON();
                 deletedParseObject.className = className;
               }
+              if (
+                (deletedParseObject.className === '_User' ||
+                  deletedParseObject.className === '_Session') &&
+                !client.hasMasterKey
+              ) {
+                delete deletedParseObject.sessionToken;
+                delete deletedParseObject.authData;
+              }
               client.pushDelete(requestId, deletedParseObject);
             })
             .catch(error => {

--- a/src/LiveQuery/ParseLiveQueryServer.js
+++ b/src/LiveQuery/ParseLiveQueryServer.js
@@ -112,6 +112,16 @@
                   originalParseObject = res.original.toJSON();
                   originalParseObject.className = res.original.className || className;
                 }
+                if (
+                  (currentParseObject.className === '_User' ||
+                    currentParseObject.className === '_Session') &&
+                  !client.hasMasterKey
+                ) {
+                  delete currentParseObject.sessionToken;
+                  delete originalParseObject?.sessionToken;
+                  delete currentParseObject.authData;
+                  delete originalParseObject?.authData;
+                }
                 const functionName =
                   'push' + message.event.charAt(0).toUpperCase() + message.event.slice(1);
                 if (client[functionName]) {

--- /dev/null
+++ b/src/LiveQuery/ParseLiveQueryServer.js
@@ -0,0 +1,45 @@
+  it('should strip out session token in LiveQuery', async () => {
+    await reconfigureServer({
+      liveQuery: { classNames: ['_User'] },
+      startLiveQueryServer: true,
+      verbose: false,
+      silent: true,
+    });
+
+    const user = new Parse.User();
+    user.setUsername('username');
+    user.setPassword('password');
+    user.set('foo', 'bar');
+
+    const query = new Parse.Query(Parse.User);
+    query.equalTo('foo', 'bar');
+    const subscription = await query.subscribe();
+
+    const events = ['create', 'update', 'enter', 'leave', 'delete'];
+    const response = (obj, prev) => {
+      expect(obj.get('sessionToken')).toBeUndefined();
+      expect(obj.sessionToken).toBeUndefined();
+      expect(prev?.sessionToken).toBeUndefined();
+      if (prev && prev.get) {
+        expect(prev.get('sessionToken')).toBeUndefined();
+      }
+    };
+    const calls = {};
+    for (const key of events) {
+      calls[key] = response;
+      spyOn(calls, key).and.callThrough();
+      subscription.on(key, calls[key]);
+    }
+    await user.signUp();
+    user.unset('foo');
+    await user.save();
+    user.set('foo', 'bar');
+    await user.save();
+    user.set('yolo', 'bar');
+    await user.save();
+    await user.destroy();
+    await new Promise(resolve => process.nextTick(resolve));
+    for (const key of events) {
+      expect(calls[key]).toHaveBeenCalled();
+    }
+  });

--- /dev/null
+++ b/src/LiveQuery/ParseLiveQueryServer.js
@@ -0,0 +1,44 @@
+  it('should strip out authdata in LiveQuery', async () => {
+    const provider = getMockFacebookProvider();
+    Parse.User._registerAuthenticationProvider(provider);
+
+    await reconfigureServer({
+      liveQuery: { classNames: ['_User'] },
+      startLiveQueryServer: true,
+      verbose: false,
+      silent: true,
+    });
+
+    const query = new Parse.Query(Parse.User);
+    query.doesNotExist('foo');
+    const subscription = await query.subscribe();
+
+    const events = ['create', 'update', 'enter', 'leave', 'delete'];
+    const response = (obj, prev) => {
+      expect(obj.get('authData')).toBeUndefined();
+      expect(obj.authData).toBeUndefined();
+      expect(prev?.authData).toBeUndefined();
+      if (prev && prev.get) {
+        expect(prev.get('authData')).toBeUndefined();
+      }
+    };
+    const calls = {};
+    for (const key of events) {
+      calls[key] = response;
+      spyOn(calls, key).and.callThrough();
+      subscription.on(key, calls[key]);
+    }
+    const user = await Parse.User._logInWith('facebook');
+
+    user.set('foo', 'bar');
+    await user.save();
+    user.unset('foo');
+    await user.save();
+    user.set('yolo', 'bar');
+    await user.save();
+    await user.destroy();
+    await new Promise(resolve => process.nextTick(resolve));
+    for (const key of events) {
+      expect(calls[key]).toHaveBeenCalled();
+    }
+  });


Improper Privilege Management in GitHub repository ikus060/rdiffweb prior to 2.5.2.

Improper Authorization

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

Execution with Unnecessary Privileges

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

Improper Privilege Management

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

    def __init__(self, user_root, path, encoding):
        if isinstance(user_root, str):
            user_root = os.fsencode(user_root)
        if isinstance(path, str):
            path = os.fsencode(path)
        assert isinstance(user_root, bytes)
        assert isinstance(path, bytes)
        assert encoding
        self._encoding = encodings.search_function(encoding)
        assert self._encoding
        self.path = path.strip(b"/")
        if self.path:
            self.full_path = os.path.normpath(os.path.join(user_root, self.path))
        else:
            self.full_path = os.path.normpath(user_root)

        # The location of rdiff-backup-data directory.
        self._data_path = os.path.join(self.full_path, RDIFF_BACKUP_DATA)
        assert isinstance(self._data_path, bytes)
        self._increment_path = os.path.join(self._data_path, INCREMENTS)
        self.current_mirror = MetadataDict(self, CurrentMirrorEntry)
        self.error_log = MetadataDict(self, LogEntry)
        self.mirror_metadata = MetadataDict(self, MirrorMetadataEntry)
        self.file_statistics = MetadataDict(self, FileStatisticsEntry)
        self.session_statistics = MetadataDict(self, SessionStatisticsEntry)

    def get_display_name(self, path):
        """
        Return proper display name of the given path according to repository encoding and quoted characters.
        """
        assert isinstance(path, bytes)
        path = path.strip(b'/')
        if path in [b'.', b'']:
            # For repository we use either path if defined or the directory base name
            if not self.path:
                return self._decode(unquote(os.path.basename(self.full_path)))
            return self._decode(unquote(self.path))
        else:
            # For path, we use the dir name
            return self._decode(unquote(os.path.basename(path)))

    def __init_on_load__(self):
        RdiffRepo.__init__(
            self, self.user.user_root, self.repopath, encoding=self.encoding or RepoObject.DEFAULT_REPO_ENCODING
        )

    @property
    def displayname(self):
        # Repository displayName is the "repopath" too.
        return self.repopath.strip('/')

def url_for(*args, **kwargs):
    """
    Generate a url for the given endpoint, path (*args) with parameters (**kwargs)

    This could be used to generate a path with userobject and repo object

    """
    path = ""
    for chunk in args:
        if not chunk:
            continue
        if hasattr(chunk, 'owner') and hasattr(chunk, 'path'):
            # This is a RepoObject
            path += "/"
            path += chunk.owner
            path += "/"
            path += rdw_helpers.quote_url(chunk.path.strip(b"/"))
        elif hasattr(chunk, 'path'):
            # This is a DirEntry
            if chunk.path:
                path += "/"
                path += rdw_helpers.quote_url(chunk.path.strip(b"/"))
        elif chunk and isinstance(chunk, bytes):
            path += "/"
            path += rdw_helpers.quote_url(chunk.strip(b"/"))
        elif chunk and isinstance(chunk, str):
            path += "/"
            path += chunk.strip("/")
        else:
            raise ValueError('invalid positional arguments, url_for accept str, bytes or RepoPath: %r' % chunk)
    # Sort the arguments to have predictable results.
    qs = [(k, v.epoch() if hasattr(v, 'epoch') else v) for k, v in sorted(kwargs.items()) if v is not None]
    return cherrypy.url(path=path, qs=qs)

    def __init__(self, full_path, encoding):
        assert encoding, 'encoding is required'
        self._encoding = encodings.search_function(encoding)
        assert self._encoding, 'encoding must be a valid charset'

        # Validate and sanitize the full_path
        assert full_path, 'full path is required'
        self.full_path = os.fsencode(full_path) if isinstance(full_path, str) else full_path
        assert os.path.isabs(self.full_path), 'full_path must be absolute path'
        self.full_path = os.path.normpath(self.full_path)

        # The location of rdiff-backup-data directory.
        self._data_path = os.path.join(self.full_path, RDIFF_BACKUP_DATA)
        assert isinstance(self._data_path, bytes)
        self._increment_path = os.path.join(self._data_path, INCREMENTS)
        self.current_mirror = MetadataDict(self, CurrentMirrorEntry)
        self.error_log = MetadataDict(self, LogEntry)
        self.mirror_metadata = MetadataDict(self, MirrorMetadataEntry)
        self.file_statistics = MetadataDict(self, FileStatisticsEntry)
        self.session_statistics = MetadataDict(self, SessionStatisticsEntry)

    def get_display_name(self, path):
        """
        Return proper display name of the given path according to repository encoding and quoted characters.
        """
        assert isinstance(path, bytes)
        path = path.strip(b'/')
        if path in [b'.', b'']:
            # For repository the directory base name
            return self._decode(unquote(os.path.basename(self.full_path)))
        else:
            # For path, we use the dir name
            return self._decode(unquote(os.path.basename(path)))

    def __init_on_load__(self):
        # RdiffRepo required an absolute full path, When the user_root is invalid, let generate an invalid full path.
        if not self.user.user_root:
            full_path = os.path.join('/user_has_an_empty_user_root/', self.repopath.strip('/'))
        elif not os.path.isabs(self.user.user_root):
            full_path = os.path.join('/user_has_a_relative_user_root/', self.repopath.strip('/'))
        else:
            full_path = os.path.join(self.user.user_root, self.repopath.strip('/'))
        RdiffRepo.__init__(self, full_path, encoding=self.encoding or RepoObject.DEFAULT_REPO_ENCODING)

def url_for(*args, **kwargs):
    """
    Generate a url for the given endpoint, path (*args) with parameters (**kwargs)

    This could be used to generate a path with userobject and repo object

    """
    path = ""
    for chunk in args:
        if not chunk:
            continue
        if hasattr(chunk, 'owner') and hasattr(chunk, 'repopath'):
            # This is a RepoObject
            path += "/"
            path += chunk.owner
            path += "/"
            path += rdw_helpers.quote_url(chunk.repopath.strip("/"))
        elif hasattr(chunk, 'path'):
            # This is a DirEntry
            if chunk.path:
                path += "/"
                path += rdw_helpers.quote_url(chunk.path.strip(b"/"))
        elif chunk and isinstance(chunk, bytes):
            path += "/"
            path += rdw_helpers.quote_url(chunk.strip(b"/"))
        elif chunk and isinstance(chunk, str):
            path += "/"
            path += chunk.strip("/")
        else:
            raise ValueError('invalid positional arguments, url_for accept str, bytes or RepoPath: %r' % chunk)
    # Sort the arguments to have predictable results.
    qs = [(k, v.epoch() if hasattr(v, 'epoch') else v) for k, v in sorted(kwargs.items()) if v is not None]
    return cherrypy.url(path=path, qs=qs)

--- a/rdiffweb/core/librdiff.py
+++ b/rdiffweb/core/librdiff.py
@@ -1,18 +1,13 @@
-    def __init__(self, user_root, path, encoding):
-        if isinstance(user_root, str):
-            user_root = os.fsencode(user_root)
-        if isinstance(path, str):
-            path = os.fsencode(path)
-        assert isinstance(user_root, bytes)
-        assert isinstance(path, bytes)
-        assert encoding
+    def __init__(self, full_path, encoding):
+        assert encoding, 'encoding is required'
         self._encoding = encodings.search_function(encoding)
-        assert self._encoding
-        self.path = path.strip(b"/")
-        if self.path:
-            self.full_path = os.path.normpath(os.path.join(user_root, self.path))
-        else:
-            self.full_path = os.path.normpath(user_root)
+        assert self._encoding, 'encoding must be a valid charset'
+
+        # Validate and sanitize the full_path
+        assert full_path, 'full path is required'
+        self.full_path = os.fsencode(full_path) if isinstance(full_path, str) else full_path
+        assert os.path.isabs(self.full_path), 'full_path must be absolute path'
+        self.full_path = os.path.normpath(self.full_path)

         # The location of rdiff-backup-data directory.
         self._data_path = os.path.join(self.full_path, RDIFF_BACKUP_DATA)

--- a/rdiffweb/core/librdiff.py
+++ b/rdiffweb/core/librdiff.py
@@ -5,10 +5,8 @@
         assert isinstance(path, bytes)
         path = path.strip(b'/')
         if path in [b'.', b'']:
-            # For repository we use either path if defined or the directory base name
-            if not self.path:
-                return self._decode(unquote(os.path.basename(self.full_path)))
-            return self._decode(unquote(self.path))
+            # For repository the directory base name
+            return self._decode(unquote(os.path.basename(self.full_path)))
         else:
             # For path, we use the dir name
             return self._decode(unquote(os.path.basename(path)))

--- a/rdiffweb/core/model/_repo.py
+++ b/rdiffweb/core/model/_repo.py
@@ -1,9 +1,9 @@
     def __init_on_load__(self):
-        RdiffRepo.__init__(
-            self, self.user.user_root, self.repopath, encoding=self.encoding or RepoObject.DEFAULT_REPO_ENCODING
-        )
-
-    @property
-    def displayname(self):
-        # Repository displayName is the "repopath" too.
-        return self.repopath.strip('/')
+        # RdiffRepo required an absolute full path, When the user_root is invalid, let generate an invalid full path.
+        if not self.user.user_root:
+            full_path = os.path.join('/user_has_an_empty_user_root/', self.repopath.strip('/'))
+        elif not os.path.isabs(self.user.user_root):
+            full_path = os.path.join('/user_has_a_relative_user_root/', self.repopath.strip('/'))
+        else:
+            full_path = os.path.join(self.user.user_root, self.repopath.strip('/'))
+        RdiffRepo.__init__(self, full_path, encoding=self.encoding or RepoObject.DEFAULT_REPO_ENCODING)

--- a/rdiffweb/core/rdw_templating.py
+++ b/rdiffweb/core/rdw_templating.py
@@ -9,12 +9,12 @@
     for chunk in args:
         if not chunk:
             continue
-        if hasattr(chunk, 'owner') and hasattr(chunk, 'path'):
+        if hasattr(chunk, 'owner') and hasattr(chunk, 'repopath'):
             # This is a RepoObject
             path += "/"
             path += chunk.owner
             path += "/"
-            path += rdw_helpers.quote_url(chunk.path.strip(b"/"))
+            path += rdw_helpers.quote_url(chunk.repopath.strip("/"))
         elif hasattr(chunk, 'path'):
             # This is a DirEntry
             if chunk.path:


SRS is a real-time video server supporting RTMP, WebRTC, HLS, HTTP-FLV, SRT, MPEG-DASH, and GB28181. Prior to versions 5.0.157, 5.0-b1, and 6.0.48, SRS's `api-server` server is vulnerable to a drive-by command injection. An attacker may send a request to the `/api/v1/snapshots` endpoint containing any commands to be executed as part of the body of the POST request. This issue may lead to Remote Code Execution (RCE). Versions 5.0.157, 5.0-b1, and 6.0.48 contain a fix.

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.

func (v *SnapshotJob) do(ffmpegPath, inputUrl string) (err error) {
	outputPicDir := path.Join(StaticDir, v.App)
	if err = os.MkdirAll(outputPicDir, 0777); err != nil {
		log.Println(fmt.Sprintf("create snapshot image dir:%v failed, err is %v", outputPicDir, err))
		return
	}

	normalPicPath := path.Join(outputPicDir, fmt.Sprintf("%v", v.Stream)+"-%03d.png")
	bestPng := path.Join(outputPicDir, fmt.Sprintf("%v-best.png", v.Stream))

	param := fmt.Sprintf("%v -i %v -vf fps=1 -vcodec png -f image2 -an -y -vframes %v -y %v", ffmpegPath, inputUrl, v.vframes, normalPicPath)
	log.Println(fmt.Sprintf("start snapshot, cmd param=%v", param))
	timeoutCtx, _ := context.WithTimeout(v.cancelCtx, v.timeout)
	cmd := exec.CommandContext(timeoutCtx, "/bin/bash", "-c", param)
	if err = cmd.Run(); err != nil {
		log.Println(fmt.Sprintf("run snapshot %v cmd failed, err is %v", v.Tag(), err))
		return
	}

	bestFileSize := int64(0)
	for i := 1; i <= v.vframes; i++ {
		pic := path.Join(outputPicDir, fmt.Sprintf("%v-%03d.png", v.Stream, i))
		fi, err := os.Stat(pic)
		if err != nil {
			log.Println(fmt.Sprintf("stat pic:%v failed, err is %v", pic, err))
			continue
		}
		if bestFileSize == 0 {
			bestFileSize = fi.Size()
		} else if fi.Size() > bestFileSize {
			os.Remove(bestPng)
			os.Symlink(pic, bestPng)
			bestFileSize = fi.Size()
		}
	}
	log.Println(fmt.Sprintf("%v the best thumbnail is %v", v.Tag(), bestPng))
	return
}

func (v *SnapshotJob) do(ffmpegPath, inputUrl string) (err error) {
	outputPicDir := path.Join(StaticDir, v.App)
	if err = os.MkdirAll(outputPicDir, 0777); err != nil {
		log.Println(fmt.Sprintf("create snapshot image dir:%v failed, err is %v", outputPicDir, err))
		return
	}

	normalPicPath := path.Join(outputPicDir, fmt.Sprintf("%v", v.Stream)+"-%03d.png")
	bestPng := path.Join(outputPicDir, fmt.Sprintf("%v-best.png", v.Stream))

	params := []string{
		"-i", inputUrl,
		"-vf", "fps=1",
		"-vcodec", "png",
		"-f", "image2",
		"-an",
		"-vframes", strconv.Itoa(v.vframes),
		"-y", normalPicPath,
	}
	log.Println(fmt.Sprintf("start snapshot, cmd param=%v %v", ffmpegPath, strings.Join(params, " ")))
	timeoutCtx, _ := context.WithTimeout(v.cancelCtx, v.timeout)
	cmd := exec.CommandContext(timeoutCtx, ffmpegPath, params...)
	if err = cmd.Run(); err != nil {
		log.Println(fmt.Sprintf("run snapshot %v cmd failed, err is %v", v.Tag(), err))
		return
	}

	bestFileSize := int64(0)
	for i := 1; i <= v.vframes; i++ {
		pic := path.Join(outputPicDir, fmt.Sprintf("%v-%03d.png", v.Stream, i))
		fi, err := os.Stat(pic)
		if err != nil {
			log.Println(fmt.Sprintf("stat pic:%v failed, err is %v", pic, err))
			continue
		}
		if bestFileSize == 0 {
			bestFileSize = fi.Size()
		} else if fi.Size() > bestFileSize {
			os.Remove(bestPng)
			os.Symlink(pic, bestPng)
			bestFileSize = fi.Size()
		}
	}
	log.Println(fmt.Sprintf("%v the best thumbnail is %v", v.Tag(), bestPng))
	return
}

--- a/trunk/research/api-server/server.go
+++ b/trunk/research/api-server/server.go
@@ -8,10 +8,18 @@
 	normalPicPath := path.Join(outputPicDir, fmt.Sprintf("%v", v.Stream)+"-%03d.png")
 	bestPng := path.Join(outputPicDir, fmt.Sprintf("%v-best.png", v.Stream))

-	param := fmt.Sprintf("%v -i %v -vf fps=1 -vcodec png -f image2 -an -y -vframes %v -y %v", ffmpegPath, inputUrl, v.vframes, normalPicPath)
-	log.Println(fmt.Sprintf("start snapshot, cmd param=%v", param))
+	params := []string{
+		"-i", inputUrl,
+		"-vf", "fps=1",
+		"-vcodec", "png",
+		"-f", "image2",
+		"-an",
+		"-vframes", strconv.Itoa(v.vframes),
+		"-y", normalPicPath,
+	}
+	log.Println(fmt.Sprintf("start snapshot, cmd param=%v %v", ffmpegPath, strings.Join(params, " ")))
 	timeoutCtx, _ := context.WithTimeout(v.cancelCtx, v.timeout)
-	cmd := exec.CommandContext(timeoutCtx, "/bin/bash", "-c", param)
+	cmd := exec.CommandContext(timeoutCtx, ffmpegPath, params...)
 	if err = cmd.Run(); err != nil {
 		log.Println(fmt.Sprintf("run snapshot %v cmd failed, err is %v", v.Tag(), err))
 		return


The npm package "tar" (aka node-tar) before versions 6.1.2, 5.0.7, 4.4.15, and 3.2.3 has an arbitrary File Creation/Overwrite vulnerability via insufficient symlink protection. `node-tar` aims to guarantee that any file whose location would be modified by a symbolic link is not extracted. This is, in part, achieved by ensuring that extracted directories are not symlinks. Additionally, in order to prevent unnecessary `stat` calls to determine whether a given path is a directory, paths are cached when directories are created. This logic was insufficient when extracting tar files that contained both a directory and a symlink with the same name as the directory. This order of operations resulted in the directory being created and added to the `node-tar` directory cache. When a directory is present in the directory cache, subsequent calls to mkdir for that directory are skipped. However, this is also where `node-tar` checks for symlinks occur. By first creating a directory, and then replacing that directory with a symlink, it was thus possible to bypass `node-tar` symlink checks on directories, essentially allowing an untrusted tar file to symlink into an arbitrary location and subsequently extracting arbitrary files into that location, thus allowing arbitrary file creation and overwrite. This issue was addressed in releases 3.2.3, 4.4.15, 5.0.7 and 6.1.2.

Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.

  [CHECKFS] (entry) {
    this[PEND]()
    this[MKDIR](path.dirname(entry.absolute), this.dmode, er => {
      if (er)
        return this[ONERROR](er, entry)
      fs.lstat(entry.absolute, (er, st) => {
        if (st && (this.keep || this.newer && st.mtime > entry.mtime))
          this[SKIP](entry)
        else if (er || this[ISREUSABLE](entry, st))
          this[MAKEFS](null, entry)
        else if (st.isDirectory()) {
          if (entry.type === 'Directory') {
            if (!entry.mode || (st.mode & 0o7777) === entry.mode)
              this[MAKEFS](null, entry)
            else
              fs.chmod(entry.absolute, entry.mode, er => this[MAKEFS](er, entry))
          } else
            fs.rmdir(entry.absolute, er => this[MAKEFS](er, entry))
        } else
          unlinkFile(entry.absolute, er => this[MAKEFS](er, entry))
      })
    })
  }

  [CHECKFS] (entry) {
    const er = this[MKDIR](path.dirname(entry.absolute), this.dmode)
    if (er)
      return this[ONERROR](er, entry)
    try {
      const st = fs.lstatSync(entry.absolute)
      if (this.keep || this.newer && st.mtime > entry.mtime)
        return this[SKIP](entry)
      else if (this[ISREUSABLE](entry, st))
        return this[MAKEFS](null, entry)
      else {
        try {
          if (st.isDirectory()) {
            if (entry.type === 'Directory') {
              if (entry.mode && (st.mode & 0o7777) !== entry.mode)
                fs.chmodSync(entry.absolute, entry.mode)
            } else
              fs.rmdirSync(entry.absolute)
          } else
            unlinkFileSync(entry.absolute)
          return this[MAKEFS](null, entry)
        } catch (er) {
          return this[ONERROR](er, entry)
        }
      }
    } catch (er) {
      return this[MAKEFS](null, entry)
    }
  }

  [CHECKFS] (entry) {
    this[PEND]()

    // if we are not creating a directory, and the path is in the dirCache,
    // then that means we are about to delete the directory we created
    // previously, and it is no longer going to be a directory, and neither
    // is any of its children.
    if (entry.type !== 'Directory') {
      for (const path of this.dirCache.keys()) {
        if (path === entry.absolute ||
            path.indexOf(entry.absolute + '/') === 0 ||
            path.indexOf(entry.absolute + '\\') === 0)
          this.dirCache.delete(path)
      }
    }

    this[MKDIR](path.dirname(entry.absolute), this.dmode, er => {
      if (er)
        return this[ONERROR](er, entry)
      fs.lstat(entry.absolute, (er, st) => {
        if (st && (this.keep || this.newer && st.mtime > entry.mtime))
          this[SKIP](entry)
        else if (er || this[ISREUSABLE](entry, st))
          this[MAKEFS](null, entry)
        else if (st.isDirectory()) {
          if (entry.type === 'Directory') {
            if (!entry.mode || (st.mode & 0o7777) === entry.mode)
              this[MAKEFS](null, entry)
            else
              fs.chmod(entry.absolute, entry.mode, er => this[MAKEFS](er, entry))
          } else
            fs.rmdir(entry.absolute, er => this[MAKEFS](er, entry))
        } else
          unlinkFile(entry.absolute, er => this[MAKEFS](er, entry))
      })
    })
  }

  [CHECKFS] (entry) {
    if (entry.type !== 'Directory') {
      for (const path of this.dirCache.keys()) {
        if (path === entry.absolute ||
            path.indexOf(entry.absolute + '/') === 0 ||
            path.indexOf(entry.absolute + '\\') === 0)
          this.dirCache.delete(path)
      }
    }

    const er = this[MKDIR](path.dirname(entry.absolute), this.dmode)
    if (er)
      return this[ONERROR](er, entry)
    try {
      const st = fs.lstatSync(entry.absolute)
      if (this.keep || this.newer && st.mtime > entry.mtime)
        return this[SKIP](entry)
      else if (this[ISREUSABLE](entry, st))
        return this[MAKEFS](null, entry)
      else {
        try {
          if (st.isDirectory()) {
            if (entry.type === 'Directory') {
              if (entry.mode && (st.mode & 0o7777) !== entry.mode)
                fs.chmodSync(entry.absolute, entry.mode)
            } else
              fs.rmdirSync(entry.absolute)
          } else
            unlinkFileSync(entry.absolute)
          return this[MAKEFS](null, entry)
        } catch (er) {
          return this[ONERROR](er, entry)
        }
      }
    } catch (er) {
      return this[MAKEFS](null, entry)
    }
  }

--- a/lib/unpack.js
+++ b/lib/unpack.js
@@ -1,5 +1,19 @@
   [CHECKFS] (entry) {
     this[PEND]()
+
+    // if we are not creating a directory, and the path is in the dirCache,
+    // then that means we are about to delete the directory we created
+    // previously, and it is no longer going to be a directory, and neither
+    // is any of its children.
+    if (entry.type !== 'Directory') {
+      for (const path of this.dirCache.keys()) {
+        if (path === entry.absolute ||
+            path.indexOf(entry.absolute + '/') === 0 ||
+            path.indexOf(entry.absolute + '\\') === 0)
+          this.dirCache.delete(path)
+      }
+    }
+
     this[MKDIR](path.dirname(entry.absolute), this.dmode, er => {
       if (er)
         return this[ONERROR](er, entry)

--- a/lib/unpack.js
+++ b/lib/unpack.js
@@ -1,4 +1,13 @@
   [CHECKFS] (entry) {
+    if (entry.type !== 'Directory') {
+      for (const path of this.dirCache.keys()) {
+        if (path === entry.absolute ||
+            path.indexOf(entry.absolute + '/') === 0 ||
+            path.indexOf(entry.absolute + '\\') === 0)
+          this.dirCache.delete(path)
+      }
+    }
+
     const er = this[MKDIR](path.dirname(entry.absolute), this.dmode)
     if (er)
       return this[ONERROR](er, entry)

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

cd /workspace/node-tar
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
node ./test/unpack.js

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

cd /workspace/node-tar
git apply --whitespace=nowarn /workspace/fix.patch
node ./test/unpack.js


Grafana is an open source data visualization platform. In affected versions unauthenticated and authenticated users are able to view the snapshot with the lowest database key by accessing the literal paths: /dashboard/snapshot/:key, or /api/snapshots/:key. If the snapshot "public_mode" configuration setting is set to true (vs default of false), unauthenticated users are able to delete the snapshot with the lowest database key by accessing the literal path: /api/snapshots-delete/:deleteKey. Regardless of the snapshot "public_mode" setting, authenticated users are able to delete the snapshot with the lowest database key by accessing the literal paths: /api/snapshots/:key, or /api/snapshots-delete/:deleteKey. The combination of deletion and viewing enables a complete walk through all snapshot data while resulting in complete snapshot data loss. This issue has been resolved in versions 8.1.6 and 7.5.11. If for some reason you cannot upgrade you can use a reverse proxy or similar to block access to the literal paths: /api/snapshots/:key, /api/snapshots-delete/:deleteKey, /dashboard/snapshot/:key, and /api/snapshots/:key. They have no normal function and can be disabled without side effects.

Missing Authorization

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

Authorization Bypass Through User-Controlled Key

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

func GetDashboardSnapshot(c *models.ReqContext) response.Response {
	key := c.Params(":key")
	query := &models.GetDashboardSnapshotQuery{Key: key}

	err := bus.Dispatch(query)
	if err != nil {
		return response.Error(500, "Failed to get dashboard snapshot", err)
	}

	snapshot := query.Result

	// expired snapshots should also be removed from db
	if snapshot.Expires.Before(time.Now()) {
		return response.Error(404, "Dashboard snapshot not found", err)
	}

	dashboard, err := snapshot.DashboardJSON()
	if err != nil {
		return response.Error(500, "Failed to get dashboard data for dashboard snapshot", err)
	}

	dto := dtos.DashboardFullWithMeta{
		Dashboard: dashboard,
		Meta: dtos.DashboardMeta{
			Type:       models.DashTypeSnapshot,
			IsSnapshot: true,
			Created:    snapshot.Created,
			Expires:    snapshot.Expires,
		},
	}

	metrics.MApiDashboardSnapshotGet.Inc()

	return response.JSON(200, dto).SetHeader("Cache-Control", "public, max-age=3600")
}

func DeleteDashboardSnapshotByDeleteKey(c *models.ReqContext) response.Response {
	key := c.Params(":deleteKey")

	query := &models.GetDashboardSnapshotQuery{DeleteKey: key}

	err := bus.Dispatch(query)
	if err != nil {
		return response.Error(500, "Failed to get dashboard snapshot", err)
	}

	if query.Result.External {
		err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl)
		if err != nil {
			return response.Error(500, "Failed to delete external dashboard", err)
		}
	}

	cmd := &models.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}

	if err := bus.Dispatch(cmd); err != nil {
		return response.Error(500, "Failed to delete dashboard snapshot", err)
	}

	return response.JSON(200, util.DynMap{
		"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.",
		"id":      query.Result.Id,
	})
}

func DeleteDashboardSnapshot(c *models.ReqContext) response.Response {
	key := c.Params(":key")

	query := &models.GetDashboardSnapshotQuery{Key: key}

	err := bus.Dispatch(query)
	if err != nil {
		return response.Error(500, "Failed to get dashboard snapshot", err)
	}
	if query.Result == nil {
		return response.Error(404, "Failed to get dashboard snapshot", nil)
	}

	dashboard, err := query.Result.DashboardJSON()
	if err != nil {
		return response.Error(500, "Failed to get dashboard data for dashboard snapshot", err)
	}
	dashboardID := dashboard.Get("id").MustInt64()

	guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
	canEdit, err := guardian.CanEdit()
	if err != nil {
		return response.Error(500, "Error while checking permissions for snapshot", err)
	}

	if !canEdit && query.Result.UserId != c.SignedInUser.UserId {
		return response.Error(403, "Access denied to this snapshot", nil)
	}

	if query.Result.External {
		err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl)
		if err != nil {
			return response.Error(500, "Failed to delete external dashboard", err)
		}
	}

	cmd := &models.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}

	if err := bus.Dispatch(cmd); err != nil {
		return response.Error(500, "Failed to delete dashboard snapshot", err)
	}

	return response.JSON(200, util.DynMap{
		"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.",
		"id":      query.Result.Id,
	})
}

func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if t, ok := r.routers[req.Method]; ok {
		// Fast match for static routes
		leaf := r.getLeaf(req.Method, req.URL.Path)
		if leaf != nil {
			leaf.handle(rw, req, nil)
			return
		}

		h, p, ok := t.Match(req.URL.EscapedPath())
		if ok {
			if splat, ok := p["*0"]; ok {
				p["*"] = splat // Easy name.
			}
			h(rw, req, p)
			return
		}
	}

	r.notFound(rw, req)
}

func GetDashboardSnapshot(c *models.ReqContext) response.Response {
	key := c.Params(":key")
	if len(key) == 0 {
		return response.Error(404, "Snapshot not found", nil)
	}
	query := &models.GetDashboardSnapshotQuery{Key: key}

	err := bus.Dispatch(query)
	if err != nil {
		return response.Error(500, "Failed to get dashboard snapshot", err)
	}

	snapshot := query.Result

	// expired snapshots should also be removed from db
	if snapshot.Expires.Before(time.Now()) {
		return response.Error(404, "Dashboard snapshot not found", err)
	}

	dashboard, err := snapshot.DashboardJSON()
	if err != nil {
		return response.Error(500, "Failed to get dashboard data for dashboard snapshot", err)
	}

	dto := dtos.DashboardFullWithMeta{
		Dashboard: dashboard,
		Meta: dtos.DashboardMeta{
			Type:       models.DashTypeSnapshot,
			IsSnapshot: true,
			Created:    snapshot.Created,
			Expires:    snapshot.Expires,
		},
	}

	metrics.MApiDashboardSnapshotGet.Inc()

	return response.JSON(200, dto).SetHeader("Cache-Control", "public, max-age=3600")
}

func DeleteDashboardSnapshotByDeleteKey(c *models.ReqContext) response.Response {
	key := c.Params(":deleteKey")
	if len(key) == 0 {
		return response.Error(404, "Snapshot not found", nil)
	}

	query := &models.GetDashboardSnapshotQuery{DeleteKey: key}

	err := bus.Dispatch(query)
	if err != nil {
		return response.Error(500, "Failed to get dashboard snapshot", err)
	}

	if query.Result.External {
		err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl)
		if err != nil {
			return response.Error(500, "Failed to delete external dashboard", err)
		}
	}

	cmd := &models.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}

	if err := bus.Dispatch(cmd); err != nil {
		return response.Error(500, "Failed to delete dashboard snapshot", err)
	}

	return response.JSON(200, util.DynMap{
		"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.",
		"id":      query.Result.Id,
	})
}

func DeleteDashboardSnapshot(c *models.ReqContext) response.Response {
	key := c.Params(":key")
	if len(key) == 0 {
		return response.Error(404, "Snapshot not found", nil)
	}

	query := &models.GetDashboardSnapshotQuery{Key: key}

	err := bus.Dispatch(query)
	if err != nil {
		return response.Error(500, "Failed to get dashboard snapshot", err)
	}
	if query.Result == nil {
		return response.Error(404, "Failed to get dashboard snapshot", nil)
	}

	dashboard, err := query.Result.DashboardJSON()
	if err != nil {
		return response.Error(500, "Failed to get dashboard data for dashboard snapshot", err)
	}
	dashboardID := dashboard.Get("id").MustInt64()

	guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
	canEdit, err := guardian.CanEdit()
	if err != nil {
		return response.Error(500, "Error while checking permissions for snapshot", err)
	}

	if !canEdit && query.Result.UserId != c.SignedInUser.UserId {
		return response.Error(403, "Access denied to this snapshot", nil)
	}

	if query.Result.External {
		err := deleteExternalDashboardSnapshot(query.Result.ExternalDeleteUrl)
		if err != nil {
			return response.Error(500, "Failed to delete external dashboard", err)
		}
	}

	cmd := &models.DeleteDashboardSnapshotCommand{DeleteKey: query.Result.DeleteKey}

	if err := bus.Dispatch(cmd); err != nil {
		return response.Error(500, "Failed to delete dashboard snapshot", err)
	}

	return response.JSON(200, util.DynMap{
		"message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.",
		"id":      query.Result.Id,
	})
}

func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if t, ok := r.routers[req.Method]; ok {
		// Fast match for static routes
		if !strings.ContainsAny(req.URL.Path, ":*") {
			leaf := r.getLeaf(req.Method, req.URL.Path)
			if leaf != nil {
				leaf.handle(rw, req, nil)
				return
			}
		}

		h, p, ok := t.Match(req.URL.EscapedPath())
		if ok {
			if splat, ok := p["*0"]; ok {
				p["*"] = splat // Easy name.
			}
			h(rw, req, p)
			return
		}
	}

	r.notFound(rw, req)
}

--- a/pkg/api/dashboard_snapshot.go
+++ b/pkg/api/dashboard_snapshot.go
@@ -1,5 +1,8 @@
 func GetDashboardSnapshot(c *models.ReqContext) response.Response {
 	key := c.Params(":key")
+	if len(key) == 0 {
+		return response.Error(404, "Snapshot not found", nil)
+	}
 	query := &models.GetDashboardSnapshotQuery{Key: key}

 	err := bus.Dispatch(query)

--- a/pkg/api/dashboard_snapshot.go
+++ b/pkg/api/dashboard_snapshot.go
@@ -1,5 +1,8 @@
 func DeleteDashboardSnapshotByDeleteKey(c *models.ReqContext) response.Response {
 	key := c.Params(":deleteKey")
+	if len(key) == 0 {
+		return response.Error(404, "Snapshot not found", nil)
+	}

 	query := &models.GetDashboardSnapshotQuery{DeleteKey: key}

--- a/pkg/api/dashboard_snapshot.go
+++ b/pkg/api/dashboard_snapshot.go
@@ -1,5 +1,8 @@
 func DeleteDashboardSnapshot(c *models.ReqContext) response.Response {
 	key := c.Params(":key")
+	if len(key) == 0 {
+		return response.Error(404, "Snapshot not found", nil)
+	}

 	query := &models.GetDashboardSnapshotQuery{Key: key}

--- a/pkg/macaron/router.go
+++ b/pkg/macaron/router.go
@@ -1,10 +1,12 @@
 func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	if t, ok := r.routers[req.Method]; ok {
 		// Fast match for static routes
-		leaf := r.getLeaf(req.Method, req.URL.Path)
-		if leaf != nil {
-			leaf.handle(rw, req, nil)
-			return
+		if !strings.ContainsAny(req.URL.Path, ":*") {
+			leaf := r.getLeaf(req.Method, req.URL.Path)
+			if leaf != nil {
+				leaf.handle(rw, req, nil)
+				return
+			}
 		}

 		h, p, ok := t.Match(req.URL.EscapedPath())


A vulnerability in the LangChainLLM class of the run-llama/llama_index repository, version v0.12.5, allows for a Denial of Service (DoS) attack. The stream_complete method executes the llm using a thread and retrieves the result via the get_response_gen method of the StreamingGeneratorCallbackHandler class. If the thread terminates abnormally before the _llm.predict is executed, there is no exception handling for this case, leading to an infinite loop in the get_response_gen function. This can be triggered by providing an input of an incorrect type, causing the thread to terminate and the process to continue running indefinitely.

Improper Handling of Exceptional Conditions

The product does not handle or incorrectly handles an exceptional condition.

    def get_response_gen(self) -> Generator:
        while True:
            if not self._token_queue.empty():
                token = self._token_queue.get_nowait()
                yield token
            elif self._done.is_set():
                break

    def get_response_gen(self, timeout: float = 120.0) -> Generator:
        """Get response generator with timeout.

        Args:
            timeout (float): Maximum time in seconds to wait for the complete response.
                            Defaults to 120 seconds.
        """
        start_time = time.time()
        while True:
            if time.time() - start_time > timeout:
                raise TimeoutError(
                    f"Response generation timed out after {timeout} seconds"
                )

            if not self._token_queue.empty():
                token = self._token_queue.get_nowait()
                yield token
            elif self._done.is_set():
                break
            else:
                # Small sleep to prevent CPU spinning
                time.sleep(0.01)

--- a/llama-index-core/llama_index/core/langchain_helpers/streaming.py
+++ b/llama-index-core/llama_index/core/langchain_helpers/streaming.py
@@ -1,7 +1,22 @@
-    def get_response_gen(self) -> Generator:
+    def get_response_gen(self, timeout: float = 120.0) -> Generator:
+        """Get response generator with timeout.
+
+        Args:
+            timeout (float): Maximum time in seconds to wait for the complete response.
+                            Defaults to 120 seconds.
+        """
+        start_time = time.time()
         while True:
+            if time.time() - start_time > timeout:
+                raise TimeoutError(
+                    f"Response generation timed out after {timeout} seconds"
+                )
+
             if not self._token_queue.empty():
                 token = self._token_queue.get_nowait()
                 yield token
             elif self._done.is_set():
                 break
+            else:
+                # Small sleep to prevent CPU spinning
+                time.sleep(0.01)


Gogs is an open source self-hosted Git service. A malicious user is able to write a file to an arbitrary path on the server to gain SSH access to the server. The vulnerability is fixed in 0.13.1.

External Control of File Name or Path

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

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

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

func GetContents(c *context.APIContext) {
	repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
	gitRepo, err := git.Open(repoPath)
	if err != nil {
		c.Error(err, "open repository")
		return
	}

	ref := c.Query("ref")
	if ref == "" {
		ref = c.Repo.Repository.DefaultBranch
	}

	commit, err := gitRepo.CatFileCommit(ref)
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get commit")
		return
	}

	treePath := c.Params("*")
	entry, err := commit.TreeEntry(treePath)
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get tree entry")
		return
	}

	if !entry.IsTree() {
		content, err := toRepoContent(c, ref, treePath, commit, entry)
		if err != nil {
			c.Errorf(err, "convert %q to repoContent", treePath)
			return
		}

		c.JSONSuccess(content)
		return
	}

	// The entry is a directory
	dir, err := gitRepo.LsTree(entry.ID().String())
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get tree")
		return
	}

	entries, err := dir.Entries()
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "list entries")
		return
	}

	if len(entries) == 0 {
		c.JSONSuccess([]string{})
		return
	}

	contents := make([]*repoContent, 0, len(entries))
	for _, entry := range entries {
		subpath := path.Join(treePath, entry.Name())
		content, err := toRepoContent(c, ref, subpath, commit, entry)
		if err != nil {
			c.Errorf(err, "convert %q to repoContent", subpath)
			return
		}

		contents = append(contents, content)
	}
	c.JSONSuccess(contents)
}

func PutContents(c *context.APIContext, r PutContentsRequest) {
	content, err := base64.StdEncoding.DecodeString(r.Content)
	if err != nil {
		c.Error(err, "decoding base64")
		return
	}

	if r.Branch == "" {
		r.Branch = c.Repo.Repository.DefaultBranch
	}
	treePath := c.Params("*")
	err = c.Repo.Repository.UpdateRepoFile(
		c.User,
		database.UpdateRepoFileOptions{
			OldBranch:   c.Repo.Repository.DefaultBranch,
			NewBranch:   r.Branch,
			OldTreeName: treePath,
			NewTreeName: treePath,
			Message:     r.Message,
			Content:     string(content),
		},
	)
	if err != nil {
		c.Error(err, "updating repository file")
		return
	}

	repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
	gitRepo, err := git.Open(repoPath)
	if err != nil {
		c.Error(err, "open repository")
		return
	}

	commit, err := gitRepo.CatFileCommit(r.Branch)
	if err != nil {
		c.Error(err, "get file commit")
		return
	}

	entry, err := commit.TreeEntry(treePath)
	if err != nil {
		c.Error(err, "get tree entry")
		return
	}

	apiContent, err := toRepoContent(c, r.Branch, treePath, commit, entry)
	if err != nil {
		c.Error(err, "convert to *repoContent")
		return
	}

	apiCommit, err := gitCommitToAPICommit(commit, c)
	if err != nil {
		c.Error(err, "convert to *api.Commit")
		return
	}

	c.JSON(
		http.StatusCreated,
		map[string]any{
			"content": apiContent,
			"commit":  apiCommit,
		},
	)
}

func GetContents(c *context.APIContext) {
	repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
	gitRepo, err := git.Open(repoPath)
	if err != nil {
		c.Error(err, "open repository")
		return
	}

	ref := c.Query("ref")
	if ref == "" {
		ref = c.Repo.Repository.DefaultBranch
	}

	commit, err := gitRepo.CatFileCommit(ref)
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get commit")
		return
	}

	// \ud83d\udea8 SECURITY: Prevent path traversal.
	treePath := pathutil.Clean(c.Params("*"))
	entry, err := commit.TreeEntry(treePath)
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get tree entry")
		return
	}

	if !entry.IsTree() {
		content, err := toRepoContent(c, ref, treePath, commit, entry)
		if err != nil {
			c.Errorf(err, "convert %q to repoContent", treePath)
			return
		}

		c.JSONSuccess(content)
		return
	}

	// The entry is a directory
	dir, err := gitRepo.LsTree(entry.ID().String())
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "get tree")
		return
	}

	entries, err := dir.Entries()
	if err != nil {
		c.NotFoundOrError(gitutil.NewError(err), "list entries")
		return
	}

	if len(entries) == 0 {
		c.JSONSuccess([]string{})
		return
	}

	contents := make([]*repoContent, 0, len(entries))
	for _, entry := range entries {
		subpath := path.Join(treePath, entry.Name())
		content, err := toRepoContent(c, ref, subpath, commit, entry)
		if err != nil {
			c.Errorf(err, "convert %q to repoContent", subpath)
			return
		}

		contents = append(contents, content)
	}
	c.JSONSuccess(contents)
}

func PutContents(c *context.APIContext, r PutContentsRequest) {
	content, err := base64.StdEncoding.DecodeString(r.Content)
	if err != nil {
		c.Error(err, "decoding base64")
		return
	}

	if r.Branch == "" {
		r.Branch = c.Repo.Repository.DefaultBranch
	}

	// \ud83d\udea8 SECURITY: Prevent path traversal.
	treePath := pathutil.Clean(c.Params("*"))

	err = c.Repo.Repository.UpdateRepoFile(
		c.User,
		database.UpdateRepoFileOptions{
			OldBranch:   c.Repo.Repository.DefaultBranch,
			NewBranch:   r.Branch,
			OldTreeName: treePath,
			NewTreeName: treePath,
			Message:     r.Message,
			Content:     string(content),
		},
	)
	if err != nil {
		c.Error(err, "updating repository file")
		return
	}

	repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
	gitRepo, err := git.Open(repoPath)
	if err != nil {
		c.Error(err, "open repository")
		return
	}

	commit, err := gitRepo.CatFileCommit(r.Branch)
	if err != nil {
		c.Error(err, "get file commit")
		return
	}

	entry, err := commit.TreeEntry(treePath)
	if err != nil {
		c.Error(err, "get tree entry")
		return
	}

	apiContent, err := toRepoContent(c, r.Branch, treePath, commit, entry)
	if err != nil {
		c.Error(err, "convert to *repoContent")
		return
	}

	apiCommit, err := gitCommitToAPICommit(commit, c)
	if err != nil {
		c.Error(err, "convert to *api.Commit")
		return
	}

	c.JSON(
		http.StatusCreated,
		map[string]any{
			"content": apiContent,
			"commit":  apiCommit,
		},
	)
}

--- a/internal/route/api/v1/repo/contents.go
+++ b/internal/route/api/v1/repo/contents.go
@@ -17,7 +17,8 @@
 		return
 	}

-	treePath := c.Params("*")
+	// \ud83d\udea8 SECURITY: Prevent path traversal.
+	treePath := pathutil.Clean(c.Params("*"))
 	entry, err := commit.TreeEntry(treePath)
 	if err != nil {
 		c.NotFoundOrError(gitutil.NewError(err), "get tree entry")

--- a/internal/route/api/v1/repo/contents.go
+++ b/internal/route/api/v1/repo/contents.go
@@ -8,7 +8,10 @@
 	if r.Branch == "" {
 		r.Branch = c.Repo.Repository.DefaultBranch
 	}
-	treePath := c.Params("*")
+
+	// \ud83d\udea8 SECURITY: Prevent path traversal.
+	treePath := pathutil.Clean(c.Params("*"))
+
 	err = c.Repo.Repository.UpdateRepoFile(
 		c.User,
 		database.UpdateRepoFileOptions{


jpv (aka Json Pattern Validator) before 2.2.2 does not properly validate input, as demonstrated by a corrupted array.

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.

const compare = (value, pattern, options) => {
    /*
    * Special for debugging
    * */
    const res = (result) => {
        let val = '';
        if (!pattern || ((typeof pattern !== 'object') && (typeof pattern !== 'string') && typeof pattern !== 'function')) {
            val = String(pattern)
        } else if (pattern.constructor === JpvObject) {
            val = `operator "${pattern.type}": ${JSON.stringify(pattern.value)}`;
        } else {
            JSON.stringify(pattern)
        }

        if (typeof pattern === 'function') {
            val = pattern.toString();
        }
        if (!result && options && options.debug) {
            options.logger(`error - the value of: {${options.deepLog.join('.')}: ` +
            `${String(value)}} not matched with: ${val}`);
        }
        return result;
    };

    // simple types pattern = number | boolean | symbol | bigint
    if ((typeof pattern === 'number') || (typeof pattern === 'symbol') || (typeof pattern === 'boolean') ||
        (typeof pattern === 'bigint') || (typeof pattern === 'undefined') || (pattern === null)) {
        return res(pattern === value);
    }

    /*
    * When pattern is regex
    */
    if (pattern instanceof RegExp) {
        return res(String(value).match(pattern));
    }

    // String
    if ((typeof pattern === 'string')) {
        // Native Types
        let nativeMatches = pattern.match(/^(!)?\((.*)\)(\?)?$/i);
        if (nativeMatches !== null) {
            // eslint-disable-next-line valid-typeof
            let match = (typeof value === nativeMatches[2]);

            // ------------------------> Deprecated
            // Negation ? Operator
            if (typeof nativeMatches[3] !== 'undefined') {
                depricated('neg');
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            if (nativeMatches[1] === '!') {
                depricated('neg');
                return res(!match);
            }
            // <-------------------------

            return res(match);
        }

        // Patterns
        let logicalMatches = pattern.match(/^(!)?\[(.*)\](\?)?$/i);
        if (logicalMatches !== null) {
            const valid = comparePattern(value, logicalMatches[2]);

            // ------------------------> Deprecated
            // ? Operator
            if (typeof logicalMatches[3] !== 'undefined') {
                depricated('neg');
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            // ! Operator
            if (typeof logicalMatches[1] !== 'undefined') {
                depricated('neg');
                return res(!valid);
            }
            // <-------------------------

            return res(valid);
        }

        // ------------------------> Deprecated
        // Functional Regex
        let functionalRegexMatches = pattern.match(/^(?!=^|,)(!)?\{\/(.*)\/([a-z]*)\}(\?)?$/i);
        if (functionalRegexMatches !== null) {
            depricated('tag');
            let match = (String(value).match(new RegExp(functionalRegexMatches[2], functionalRegexMatches[3])) !== null);
            // Negation ? Operator
            if (typeof functionalRegexMatches[4] !== 'undefined') {
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            return res(functionalRegexMatches[1] === '!' ? !match : match);
        }

        // Functional Fixed
        let functionalFixedMatches = pattern.match(/^(!)?\{(.*)\}(\?)?$/i);
        if (functionalFixedMatches !== null) {
            depricated('tag');
            let match = (String(value) === String(functionalFixedMatches[2]));
            // Negation ? Operator
            if (typeof functionalFixedMatches[3] !== 'undefined') {
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            return res(functionalFixedMatches[1] === '!' ? !match : match);
        }
        // <-------------------------

        // Fixed String Comparition
        return res(value === pattern);
    }

    // Constructor is JpvObject
    if (typeof pattern === 'object' && pattern.constructor === JpvObject) {
        if (pattern.type === 'not') {
            return res(!compare(value, pattern.value, options));
        }
        if (pattern.type === 'and') {
            for (let i = 0; i < pattern.value.length; i++) {
                if (!compare(value, pattern.value[i])) {
                    return res(false);
                }
            }
            return true;
        }
        if (pattern.type === 'or') {
            for (let i = 0; i < pattern.value.length; i++) {
                if (compare(value, pattern.value[i])) {
                    return true;
                }
            }
            return res(false);
        }
        if (pattern.type === 'exact') {
            return res(value === pattern.value);
        }

        if (pattern.type === 'typeOf') {
            // eslint-disable-next-line valid-typeof
            return res(typeof value === pattern.value);
        }

        if (pattern.type === 'is') {
            return res(comparePattern(value, pattern.value));
        }
    }

    // pattern = object
    if (typeof pattern === 'object') {
        if (value !== null) {
            return res(value.constructor === pattern.constructor);
        }
        return res(value === pattern);
    }

    // pattern is a function
    if (typeof pattern === 'function') {
        return res(!!pattern(value));
    }

    throw new Error('invalid data type');
};

const compare = (value, pattern, options) => {
    /*
    * Special for debugging
    * */
    const res = (result) => {
        let val = '';
        if (!pattern || ((typeof pattern !== 'object') && (typeof pattern !== 'string') && typeof pattern !== 'function')) {
            val = String(pattern);
        } else if (pattern.constructor === JpvObject) {
            val = `operator "${pattern.type}": ${JSON.stringify(pattern.value)}`;
        } else {
            JSON.stringify(pattern);
        }

        if (typeof pattern === 'function') {
            val = pattern.toString();
        }
        if (!result && options && options.debug) {
            options.logger(`error - the value of: {${options.deepLog.join('.')}: ` +
                `${String(value)}} not matched with: ${val}`);
        }
        return result;
    };

    // simple types pattern = number | boolean | symbol | bigint
    if ((typeof pattern === 'number') || (typeof pattern === 'symbol') || (typeof pattern === 'boolean') ||
        (typeof pattern === 'bigint') || (typeof pattern === 'undefined') || (pattern === null)) {
        return res(pattern === value);
    }

    /*
    * When pattern is regex
    */
    if (pattern instanceof RegExp) {
        return res(String(value).match(pattern));
    }

    // String
    if ((typeof pattern === 'string')) {
        // Native Types
        let nativeMatches = pattern.match(/^(!)?\((.*)\)(\?)?$/i);
        if (nativeMatches !== null) {
            // eslint-disable-next-line valid-typeof
            let match = (typeof value === nativeMatches[2]);

            // ------------------------> Deprecated
            // Negation ? Operator
            if (typeof nativeMatches[3] !== 'undefined') {
                depricated('neg');
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            if (nativeMatches[1] === '!') {
                depricated('neg');
                return res(!match);
            }
            // <-------------------------

            return res(match);
        }

        // Patterns
        let logicalMatches = pattern.match(/^(!)?\[(.*)\](\?)?$/i);
        if (logicalMatches !== null) {
            const valid = comparePattern(value, logicalMatches[2]);

            // ------------------------> Deprecated
            // ? Operator
            if (typeof logicalMatches[3] !== 'undefined') {
                depricated('neg');
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            // ! Operator
            if (typeof logicalMatches[1] !== 'undefined') {
                depricated('neg');
                return res(!valid);
            }
            // <-------------------------

            return res(valid);
        }

        // ------------------------> Deprecated
        // Functional Regex
        let functionalRegexMatches = pattern.match(/^(?!=^|,)(!)?\{\/(.*)\/([a-z]*)\}(\?)?$/i);
        if (functionalRegexMatches !== null) {
            depricated('tag');
            let match = (String(value).match(new RegExp(functionalRegexMatches[2], functionalRegexMatches[3])) !== null);
            // Negation ? Operator
            if (typeof functionalRegexMatches[4] !== 'undefined') {
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            return res(functionalRegexMatches[1] === '!' ? !match : match);
        }

        // Functional Fixed
        let functionalFixedMatches = pattern.match(/^(!)?\{(.*)\}(\?)?$/i);
        if (functionalFixedMatches !== null) {
            depricated('tag');
            let match = (String(value) === String(functionalFixedMatches[2]));
            // Negation ? Operator
            if (typeof functionalFixedMatches[3] !== 'undefined') {
                if (value === null || typeof value === 'undefined' || value === '') {
                    return true;
                }
            }
            return res(functionalFixedMatches[1] === '!' ? !match : match);
        }
        // <-------------------------

        // Fixed String Comparition
        return res(value === pattern);
    }

    // Constructor is JpvObject
    if (typeof pattern === 'object' && pattern.constructor === JpvObject) {
        if (pattern.type === 'not') {
            return res(!compare(value, pattern.value, options));
        }
        if (pattern.type === 'and') {
            for (let i = 0; i < pattern.value.length; i++) {
                if (!compare(value, pattern.value[i])) {
                    return res(false);
                }
            }
            return true;
        }
        if (pattern.type === 'or') {
            for (let i = 0; i < pattern.value.length; i++) {
                if (compare(value, pattern.value[i])) {
                    return true;
                }
            }
            return res(false);
        }
        if (pattern.type === 'exact') {
            return res(value === pattern.value);
        }

        if (pattern.type === 'typeOf') {
            // eslint-disable-next-line valid-typeof
            return res(typeof value === pattern.value);
        }

        if (pattern.type === 'is') {
            return res(comparePattern(value, pattern.value));
        }
    }

    // pattern = object
    if (typeof pattern === 'object') {
        if (isArray(pattern)) {
            return res(isArray(value));
        }

        if (value !== null) {
            return res(value.constructor === pattern.constructor);
        }
        return res(value === pattern);
    }

    // pattern is a function
    if (typeof pattern === 'function') {
        return res(!!pattern(value));
    }

    throw new Error('invalid data type');
};

/**
 * Custom Is Array
 * @param value
 * @returns boolean
 */
function isArray (value) {
    if (Object.prototype.hasOwnProperty.call(Array, 'isArray')) {
        return Array.isArray(value);
    }
    if (typeof value !== 'object') {
        return false;
    }
    if (Object.prototype.toString.call(value) !== '[object Array]') {
        return false;
    }
    if (!(value instanceof Array)) {
        return false;
    }
    return true;
}

--- a/index.js
+++ b/index.js
@@ -5,20 +5,19 @@
     const res = (result) => {
         let val = '';
         if (!pattern || ((typeof pattern !== 'object') && (typeof pattern !== 'string') && typeof pattern !== 'function')) {
-            val = String(pattern)
+            val = String(pattern);
         } else if (pattern.constructor === JpvObject) {
             val = `operator "${pattern.type}": ${JSON.stringify(pattern.value)}`;
         } else {
-            JSON.stringify(pattern)
+            JSON.stringify(pattern);
         }
-

         if (typeof pattern === 'function') {
             val = pattern.toString();
         }
         if (!result && options && options.debug) {
             options.logger(`error - the value of: {${options.deepLog.join('.')}: ` +
-            `${String(value)}} not matched with: ${val}`);
+                `${String(value)}} not matched with: ${val}`);
         }
         return result;
     };
@@ -155,6 +154,10 @@

     // pattern = object
     if (typeof pattern === 'object') {
+        if (isArray(pattern)) {
+            return res(isArray(value));
+        }
+
         if (value !== null) {
             return res(value.constructor === pattern.constructor);
         }

--- /dev/null
+++ b/index.js
@@ -0,0 +1,20 @@
+/**
+ * Custom Is Array
+ * @param value
+ * @returns boolean
+ */
+function isArray (value) {
+    if (Object.prototype.hasOwnProperty.call(Array, 'isArray')) {
+        return Array.isArray(value);
+    }
+    if (typeof value !== 'object') {
+        return false;
+    }
+    if (Object.prototype.toString.call(value) !== '[object Array]') {
+        return false;
+    }
+    if (!(value instanceof Array)) {
+        return false;
+    }
+    return true;
+}

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

cd /workspace/jpv
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
npx tape test/arrays.js


MEGABOT is a fully customized Discord bot for learning and fun. The `/math` command and functionality of MEGABOT versions < 1.5.0 contains a remote code execution vulnerability due to a Python `eval()`. The vulnerability allows an attacker to inject Python code into the `expression` parameter when using `/math` in any Discord channel. This vulnerability impacts any discord guild utilizing MEGABOT. This vulnerability was fixed in  release version 1.5.0.

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.

async def math(ctx, expression):

    try:
        response = str(eval(expression))
    except:
        response = "Invalid Expression!"

    embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=response)

    await ctx.respond(embed=embed)

async def math(ctx, expression):

    try:
        tree = ast.parse(expression, mode="eval")
    except SyntaxError:
        result = "Not a valid Python expression!"
        embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=result)
        await ctx.respond(embed=embed)

    if not all(
        isinstance(
            node,
            (
                ast.Expression,
                ast.UnaryOp,
                ast.unaryop,
                ast.BinOp,
                ast.operator,
                ast.Constant,
            ),
        )
        for node in ast.walk(tree)
    ):
        result = "Not a valid mathematical expression!"
        embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=result)
        await ctx.respond(embed=embed)
    else:
        result = eval(compile(tree, filename="", mode="eval"))
        embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=result)
        await ctx.respond(embed=embed)

--- a/commands/math.py
+++ b/commands/math.py
@@ -1,10 +1,30 @@
 async def math(ctx, expression):

     try:
-        response = str(eval(expression))
-    except:
-        response = "Invalid Expression!"
+        tree = ast.parse(expression, mode="eval")
+    except SyntaxError:
+        result = "Not a valid Python expression!"
+        embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=result)
+        await ctx.respond(embed=embed)

-    embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=response)
-
-    await ctx.respond(embed=embed)
+    if not all(
+        isinstance(
+            node,
+            (
+                ast.Expression,
+                ast.UnaryOp,
+                ast.unaryop,
+                ast.BinOp,
+                ast.operator,
+                ast.Constant,
+            ),
+        )
+        for node in ast.walk(tree)
+    ):
+        result = "Not a valid mathematical expression!"
+        embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=result)
+        await ctx.respond(embed=embed)
+    else:
+        result = eval(compile(tree, filename="", mode="eval"))
+        embed = discord.Embed(color=0x5B8F3C, title="\ud83e\uddee  Math", description=result)
+        await ctx.respond(embed=embed)


A vulnerability was found in barronwaffles dwc_network_server_emulator. It has been declared as critical. This vulnerability affects the function update_profile of the file gamespy/gs_database.py. The manipulation of the argument firstname/lastname leads to sql injection. The attack can be initiated remotely. The name of the patch is f70eb21394f75019886fbc2fb536de36161ba422. It is recommended to apply a patch to fix this issue. The identifier of this vulnerability is VDB-216772.

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 update_profile(self, profileid, field):
        """Found profile id associated with session key.

        Start replacing each field one by one.
        TODO: Optimize this so it's done all in one update.
        FIXME: Possible security issue due to embedding an unsanitized
        string directly into the statement.
        """
        with Transaction(self.conn) as tx:
            q = "UPDATE users SET \"%s\" = ? WHERE profileid = ?"
            tx.nonquery(q % field[0], (field[1], profileid))

    def update_profile(self, profileid, field):
        """Found profile id associated with session key.

        Start replacing each field one by one.
        TODO: Optimize this so it's done all in one update.
        TODO: Check if other values than firstname/lastname are set using this
        """
        if field[0] in ["firstname", "lastname"]:
            with Transaction(self.conn) as tx:
                q = "UPDATE users SET \"%s\" = ? WHERE profileid = ?"
                tx.nonquery(q % field[0], (field[1], profileid))

--- a/gamespy/gs_database.py
+++ b/gamespy/gs_database.py
@@ -3,9 +3,9 @@

         Start replacing each field one by one.
         TODO: Optimize this so it's done all in one update.
-        FIXME: Possible security issue due to embedding an unsanitized
-        string directly into the statement.
+        TODO: Check if other values than firstname/lastname are set using this
         """
-        with Transaction(self.conn) as tx:
-            q = "UPDATE users SET \"%s\" = ? WHERE profileid = ?"
-            tx.nonquery(q % field[0], (field[1], profileid))
+        if field[0] in ["firstname", "lastname"]:
+            with Transaction(self.conn) as tx:
+                q = "UPDATE users SET \"%s\" = ? WHERE profileid = ?"
+                tx.nonquery(q % field[0], (field[1], profileid))


zot is ancontainer image/artifact registry based on the Open Container Initiative Distribution Specification. Prior to version 2.1.3 (corresponding to pseudoversion 1.4.4-0.20250522160828-8a99a3ed231f), when using Keycloak as an oidc provider, the clientsecret gets printed into the container stdout logs for an example at container startup. Version 2.1.3 (corresponding to pseudoversion 1.4.4-0.20250522160828-8a99a3ed231f) fixes the issue.

Insertion of Sensitive Information into Log File

The product writes sensitive information to a log file.

func (c *Config) Sanitize() *Config {
	sanitizedConfig := &Config{}

	if err := DeepCopy(c, sanitizedConfig); err != nil {
		panic(err)
	}

	if c.HTTP.Auth != nil && c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.bindPassword != "" {
		sanitizedConfig.HTTP.Auth.LDAP = &LDAPConfig{}

		if err := DeepCopy(c.HTTP.Auth.LDAP, sanitizedConfig.HTTP.Auth.LDAP); err != nil {
			panic(err)
		}

		sanitizedConfig.HTTP.Auth.LDAP.bindPassword = "******"
	}

	if c.IsEventRecorderEnabled() {
		for i, sink := range c.Extensions.Events.Sinks {
			if sink.Credentials == nil {
				continue
			}

			if err := DeepCopy(&c.Extensions.Events.Sinks[i], &sanitizedConfig.Extensions.Events.Sinks[i]); err != nil {
				panic(err)
			}

			sanitizedConfig.Extensions.Events.Sinks[i].Credentials.Password = "******"
		}
	}

	return sanitizedConfig
}

func (c *Config) Sanitize() *Config {
	sanitizedConfig := &Config{}

	if err := DeepCopy(c, sanitizedConfig); err != nil {
		panic(err)
	}

	// Sanitize HTTP config
	if c.HTTP.Auth != nil {
		// Sanitize LDAP bind password
		if c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.bindPassword != "" {
			sanitizedConfig.HTTP.Auth.LDAP = &LDAPConfig{}

			if err := DeepCopy(c.HTTP.Auth.LDAP, sanitizedConfig.HTTP.Auth.LDAP); err != nil {
				panic(err)
			}

			sanitizedConfig.HTTP.Auth.LDAP.bindPassword = "******"
		}

		// Sanitize OpenID client secrets
		if c.HTTP.Auth.OpenID != nil {
			sanitizedConfig.HTTP.Auth.OpenID = &OpenIDConfig{
				Providers: make(map[string]OpenIDProviderConfig),
			}

			for provider, config := range c.HTTP.Auth.OpenID.Providers {
				sanitizedConfig.HTTP.Auth.OpenID.Providers[provider] = OpenIDProviderConfig{
					Name:         config.Name,
					ClientID:     config.ClientID,
					ClientSecret: "******",
					KeyPath:      config.KeyPath,
					Issuer:       config.Issuer,
					Scopes:       config.Scopes,
				}
			}
		}
	}

	if c.IsEventRecorderEnabled() {
		for i, sink := range c.Extensions.Events.Sinks {
			if sink.Credentials == nil {
				continue
			}

			if err := DeepCopy(&c.Extensions.Events.Sinks[i], &sanitizedConfig.Extensions.Events.Sinks[i]); err != nil {
				panic(err)
			}

			sanitizedConfig.Extensions.Events.Sinks[i].Credentials.Password = "******"
		}
	}

	return sanitizedConfig
}

--- a/pkg/api/config/config.go
+++ b/pkg/api/config/config.go
@@ -5,14 +5,36 @@
 		panic(err)
 	}

-	if c.HTTP.Auth != nil && c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.bindPassword != "" {
-		sanitizedConfig.HTTP.Auth.LDAP = &LDAPConfig{}
+	// Sanitize HTTP config
+	if c.HTTP.Auth != nil {
+		// Sanitize LDAP bind password
+		if c.HTTP.Auth.LDAP != nil && c.HTTP.Auth.LDAP.bindPassword != "" {
+			sanitizedConfig.HTTP.Auth.LDAP = &LDAPConfig{}

-		if err := DeepCopy(c.HTTP.Auth.LDAP, sanitizedConfig.HTTP.Auth.LDAP); err != nil {
-			panic(err)
+			if err := DeepCopy(c.HTTP.Auth.LDAP, sanitizedConfig.HTTP.Auth.LDAP); err != nil {
+				panic(err)
+			}
+
+			sanitizedConfig.HTTP.Auth.LDAP.bindPassword = "******"
 		}

-		sanitizedConfig.HTTP.Auth.LDAP.bindPassword = "******"
+		// Sanitize OpenID client secrets
+		if c.HTTP.Auth.OpenID != nil {
+			sanitizedConfig.HTTP.Auth.OpenID = &OpenIDConfig{
+				Providers: make(map[string]OpenIDProviderConfig),
+			}
+
+			for provider, config := range c.HTTP.Auth.OpenID.Providers {
+				sanitizedConfig.HTTP.Auth.OpenID.Providers[provider] = OpenIDProviderConfig{
+					Name:         config.Name,
+					ClientID:     config.ClientID,
+					ClientSecret: "******",
+					KeyPath:      config.KeyPath,
+					Issuer:       config.Issuer,
+					Scopes:       config.Scopes,
+				}
+			}
+		}
 	}

 	if c.IsEventRecorderEnabled() {

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

cd /workspace/zot
rm -rf ./pkg/api/config/config_test.go
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^TestConfig$ zotregistry.dev/zot/pkg/api/config

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

cd /workspace/zot
git apply --whitespace=nowarn /workspace/fix.patch
cd pkg/api && go test -timeout 30s -v