The hhvm-attach deep link handler in Nuclide did not properly sanitize the provided hostname parameter when rendering. As a result, a malicious URL could be used to render HTML and other content inside of the editor's context, which could potentially be chained to lead to code execution. This issue affected Nuclide prior to v0.290.0.

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.

  async _debugDeepWithHhvm(params: DeepLinkParams): Promise<void> {
    const {nuclidePath, hackRoot, line, addBreakpoint, source} = params;

    if (
      typeof nuclidePath !== 'string' ||
      !nuclideUri.isRemote(nuclidePath) ||
      typeof hackRoot !== 'string'
    ) {
      atom.notifications.addError('Invalid arguments.');
      return;
    }

    const pathString = decodeURIComponent(String(nuclidePath));
    const hackRootString = decodeURIComponent(String(hackRoot));

    const startDebugger =
      params.noDebugger == null || params.noDebugger !== 'true';

    track('nuclide-attach-hhvm-deeplink', {
      pathString,
      line,
      addBreakpoint,
      source,
    });

    if (this._remoteProjectsService == null) {
      atom.notifications.addError('The remote project service is unavailable.');
      return;
    } else {
      const remoteProjectsService = this._remoteProjectsService;
      await new Promise(resolve =>
        remoteProjectsService.waitForRemoteProjectReload(resolve),
      );
    }

    const host = nuclideUri.getHostname(pathString);
    const cwd = nuclideUri.createRemoteUri(host, hackRootString);
    const notification = atom.notifications.addInfo(
      startDebugger
        ? `Connecting to ${host} and attaching debugger...`
        : `Connecting to ${host}...`,
      {
        dismissable: true,
      },
    );

    invariant(this._remoteProjectsService != null);
    const remoteConnection = await this._remoteProjectsService.createRemoteConnection(
      {
        host,
        cwd: nuclideUri.getPath(cwd),
        displayTitle: host,
      },
    );

    if (remoteConnection == null) {
      atom.notifications.addError(`Could not connect to ${host}`);
      return;
    }

    // The hostname might have changed slightly from what was passed in due to
    // DNS lookup, so create a new remote URI rather than using cwd from above.
    const hackRootUri = remoteConnection.getUriOfRemotePath(hackRootString);
    const navUri = remoteConnection.getUriOfRemotePath(
      nuclideUri.getPath(pathString),
    );

    // Set the current project root.
    if (this._cwdApi != null) {
      this._cwdApi.setCwd(hackRootUri);
    }

    // Open the script path in the editor.
    const lineNumber = parseInt(line, 10);
    if (Number.isNaN(lineNumber)) {
      goToLocation(navUri);
    } else {
      // NOTE: line numbers start at 0, so subtract 1.
      goToLocation(navUri, {line: lineNumber - 1});
    }

    if (startDebugger) {
      // Debug the remote HHVM server!
      const debuggerService = await consumeFirstProvider(
        'nuclide-debugger.remote',
      );

      if (addBreakpoint === 'true' && !Number.isNaN(lineNumber)) {
        // Insert a breakpoint if requested.
        // NOTE: Nuclide protocol breakpoint line numbers start at 0, so subtract 1.
        debuggerService.addBreakpoint(navUri, lineNumber - 1);
      }

      await debuggerService.startDebugging(
        await getAttachProcessInfo(hackRootUri),
      );
    }

    notification.dismiss();
  }
}

  async _debugDeepWithHhvm(params: DeepLinkParams): Promise<void> {
    const {nuclidePath, hackRoot, line, addBreakpoint, source} = params;

    if (
      typeof nuclidePath !== 'string' ||
      !nuclideUri.isRemote(nuclidePath) ||
      typeof hackRoot !== 'string'
    ) {
      atom.notifications.addError('Invalid arguments.');
      return;
    }

    const pathString = decodeURIComponent(String(nuclidePath));
    const hackRootString = decodeURIComponent(String(hackRoot));

    const startDebugger =
      params.noDebugger == null || params.noDebugger !== 'true';

    track('nuclide-attach-hhvm-deeplink', {
      pathString,
      line,
      addBreakpoint,
      source,
    });

    if (this._remoteProjectsService == null) {
      atom.notifications.addError('The remote project service is unavailable.');
      return;
    } else {
      const remoteProjectsService = this._remoteProjectsService;
      await new Promise(resolve =>
        remoteProjectsService.waitForRemoteProjectReload(resolve),
      );
    }

    const host = nuclideUri.getHostname(pathString);

    // Allow only valid hostname characters, per RFC 952:
    // https://tools.ietf.org/html/rfc952
    const invalidMatch = host.match(/[^A-Za-z0-9\-._]+/);
    if (invalidMatch != null) {
      atom.notifications.addError(
        'The specified host name contained invalid characters.',
      );
      return;
    }

    const cwd = nuclideUri.createRemoteUri(host, hackRootString);
    const notification = atom.notifications.addInfo(
      startDebugger
        ? `Connecting to ${host} and attaching debugger...`
        : `Connecting to ${host}...`,
      {
        dismissable: true,
      },
    );

    invariant(this._remoteProjectsService != null);
    const remoteConnection = await this._remoteProjectsService.createRemoteConnection(
      {
        host,
        cwd: nuclideUri.getPath(cwd),
        displayTitle: host,
      },
    );

    if (remoteConnection == null) {
      atom.notifications.addError(`Could not connect to ${host}`);
      return;
    }

    // The hostname might have changed slightly from what was passed in due to
    // DNS lookup, so create a new remote URI rather than using cwd from above.
    const hackRootUri = remoteConnection.getUriOfRemotePath(hackRootString);
    const navUri = remoteConnection.getUriOfRemotePath(
      nuclideUri.getPath(pathString),
    );

    // Set the current project root.
    if (this._cwdApi != null) {
      this._cwdApi.setCwd(hackRootUri);
    }

    // Open the script path in the editor.
    const lineNumber = parseInt(line, 10);
    if (Number.isNaN(lineNumber)) {
      goToLocation(navUri);
    } else {
      // NOTE: line numbers start at 0, so subtract 1.
      goToLocation(navUri, {line: lineNumber - 1});
    }

    if (startDebugger) {
      // Debug the remote HHVM server!
      const debuggerService = await consumeFirstProvider(
        'nuclide-debugger.remote',
      );

      if (addBreakpoint === 'true' && !Number.isNaN(lineNumber)) {
        // Insert a breakpoint if requested.
        // NOTE: Nuclide protocol breakpoint line numbers start at 0, so subtract 1.
        debuggerService.addBreakpoint(navUri, lineNumber - 1);
      }

      await debuggerService.startDebugging(
        await getAttachProcessInfo(hackRootUri),
      );
    }

    notification.dismiss();
  }
}

--- a/pkg/nuclide-hhvm/lib/main.js
+++ b/pkg/nuclide-hhvm/lib/main.js
@@ -34,6 +34,17 @@
     }

     const host = nuclideUri.getHostname(pathString);
+
+    // Allow only valid hostname characters, per RFC 952:
+    // https://tools.ietf.org/html/rfc952
+    const invalidMatch = host.match(/[^A-Za-z0-9\-._]+/);
+    if (invalidMatch != null) {
+      atom.notifications.addError(
+        'The specified host name contained invalid characters.',
+      );
+      return;
+    }
+
     const cwd = nuclideUri.createRemoteUri(host, hackRootString);
     const notification = atom.notifications.addInfo(
       startDebugger


JoinPath and URL.JoinPath do not remove ../ path elements appended to a relative path. For example, JoinPath("https://go.dev", "../go") returns the URL "https://go.dev/../go", despite the JoinPath documentation stating that ../ path elements are removed from the result.

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 (u *URL) JoinPath(elem ...string) *URL {
	url := *u
	if len(elem) > 0 {
		elem = append([]string{u.EscapedPath()}, elem...)
		p := path.Join(elem...)
		// path.Join will remove any trailing slashes.
		// Preserve at least one.
		if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
			p += "/"
		}
		url.setPath(p)
	}
	return &url
}

func (u *URL) JoinPath(elem ...string) *URL {
	elem = append([]string{u.EscapedPath()}, elem...)
	var p string
	if !strings.HasPrefix(elem[0], "/") {
		// Return a relative path if u is relative,
		// but ensure that it contains no ../ elements.
		elem[0] = "/" + elem[0]
		p = path.Join(elem...)[1:]
	} else {
		p = path.Join(elem...)
	}
	// path.Join will remove any trailing slashes.
	// Preserve at least one.
	if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
		p += "/"
	}
	url := *u
	url.setPath(p)
	return &url
}

--- a/src/net/url/url.go
+++ b/src/net/url/url.go
@@ -1,14 +1,20 @@
 func (u *URL) JoinPath(elem ...string) *URL {
+	elem = append([]string{u.EscapedPath()}, elem...)
+	var p string
+	if !strings.HasPrefix(elem[0], "/") {
+		// Return a relative path if u is relative,
+		// but ensure that it contains no ../ elements.
+		elem[0] = "/" + elem[0]
+		p = path.Join(elem...)[1:]
+	} else {
+		p = path.Join(elem...)
+	}
+	// path.Join will remove any trailing slashes.
+	// Preserve at least one.
+	if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
+		p += "/"
+	}
 	url := *u
-	if len(elem) > 0 {
-		elem = append([]string{u.EscapedPath()}, elem...)
-		p := path.Join(elem...)
-		// path.Join will remove any trailing slashes.
-		// Preserve at least one.
-		if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") {
-			p += "/"
-		}
-		url.setPath(p)
-	}
+	url.setPath(p)
 	return &url
 }


shelljs is vulnerable to Improper Privilege Management

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.

  function writeFileLockedDown(filePath, data) {
    fs.writeFileSync(filePath, data, {
      encoding: 'utf8',
      mode: parseInt('600', 8),
    });
  }

  writeFileLockedDown(stdoutFile, '');
  writeFileLockedDown(stderrFile, '');
  writeFileLockedDown(paramsFile, JSON.stringify(paramsToSerialize));

--- a/src/exec.js
+++ b/src/exec.js
@@ -1 +1,6 @@
-  fs.writeFileSync(paramsFile, JSON.stringify(paramsToSerialize), 'utf8');
+  function writeFileLockedDown(filePath, data) {
+    fs.writeFileSync(filePath, data, {
+      encoding: 'utf8',
+      mode: parseInt('600', 8),
+    });
+  }

--- /dev/null
+++ b/src/exec.js
@@ -0,0 +1,3 @@
+  writeFileLockedDown(stdoutFile, '');
+  writeFileLockedDown(stderrFile, '');
+  writeFileLockedDown(paramsFile, JSON.stringify(paramsToSerialize));


NiceGUI is an easy-to-use, Python-based UI framework. Prior to 2.9.1, authenticating with NiceGUI logged in the user for all browsers, including browsers in incognito mode. This vulnerability is fixed in 2.9.1.

Improper Authentication

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

        async def _handle_http(data: Dict[str, Any]) -> Dict[str, Any]:
            headers: Dict[str, Any] = data['headers']
            headers.update({'Accept-Encoding': 'identity', 'X-Forwarded-Prefix': data['prefix']})
            url = 'http://test' + data['path']
            request = self.client.build_request(
                data['method'],
                url,
                params=data['params'],
                headers=headers,
                content=data['body'],
            )
            response = await self.client.send(request)
            instance_id = data['instance-id']
            content = response.content.replace(
                b'const extraHeaders = {};',
                (f'const extraHeaders = {{ "fly-force-instance-id" : "{instance_id}" }};').encode(),
            )
            match = re.search(b'const query = ({.*?})', content)
            if match:
                new_js_object = match.group(1).decode().rstrip('}') + ", 'fly_instance_id' : '" + instance_id + "'}"
                content = content.replace(match.group(0), f'const query = {new_js_object}'.encode())
            compressed = gzip.compress(content)
            response.headers.update({'content-encoding': 'gzip', 'content-length': str(len(compressed))})
            return {
                'status_code': response.status_code,
                'headers': response.headers.multi_items(),
                'content': compressed,
            }

        async def _handle_http(data: Dict[str, Any]) -> Dict[str, Any]:
            headers: Dict[str, Any] = data['headers']
            headers.update({'Accept-Encoding': 'identity', 'X-Forwarded-Prefix': data['prefix']})
            url = 'http://test' + data['path']
            request = self.client.build_request(
                data['method'],
                url,
                params=data['params'],
                headers=headers,
                content=data['body'],
            )
            response = await self.client.send(request)
            self.client.cookies.clear()
            instance_id = data['instance-id']
            content = response.content.replace(
                b'const extraHeaders = {};',
                (f'const extraHeaders = {{ "fly-force-instance-id" : "{instance_id}" }};').encode(),
            )
            match = re.search(b'const query = ({.*?})', content)
            if match:
                new_js_object = match.group(1).decode().rstrip('}') + ", 'fly_instance_id' : '" + instance_id + "'}"
                content = content.replace(match.group(0), f'const query = {new_js_object}'.encode())
            compressed = gzip.compress(content)
            response.headers.update({'content-encoding': 'gzip', 'content-length': str(len(compressed))})
            return {
                'status_code': response.status_code,
                'headers': response.headers.multi_items(),
                'content': compressed,
            }

--- a/nicegui/air.py
+++ b/nicegui/air.py
@@ -10,6 +10,7 @@
                 content=data['body'],
             )
             response = await self.client.send(request)
+            self.client.cookies.clear()
             instance_id = data['instance-id']
             content = response.content.replace(
                 b'const extraHeaders = {};',


An issue in langchain v.0.0.171 allows a remote attacker to execute arbitrary code via a JSON file to load_prompt. This is related to __subclasses__ or a template.

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

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

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

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

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

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

def _load_prompt(config: dict) -> PromptTemplate:
    """Load the prompt template from config."""
    # Load the template from disk if necessary.
    config = _load_template("template", config)
    config = _load_output_parser(config)
    return PromptTemplate(**config)

def _load_prompt(config: dict) -> PromptTemplate:
    """Load the prompt template from config."""
    # Load the template from disk if necessary.
    config = _load_template("template", config)
    config = _load_output_parser(config)

    template_format = config.get("template_format", "f-string")
    if template_format == "jinja2":
        # Disabled due to:
        # https://github.com/langchain-ai/langchain/issues/4394
        raise ValueError(
            f"Loading templates with '{template_format}' format is no longer supported "
            f"since it can lead to arbitrary code execution. Please migrate to using "
            f"the 'f-string' template format, which does not suffer from this issue."
        )

    return PromptTemplate(**config)

--- a/libs/langchain/langchain/prompts/loading.py
+++ b/libs/langchain/langchain/prompts/loading.py
@@ -3,4 +3,15 @@
     # Load the template from disk if necessary.
     config = _load_template("template", config)
     config = _load_output_parser(config)
+
+    template_format = config.get("template_format", "f-string")
+    if template_format == "jinja2":
+        # Disabled due to:
+        # https://github.com/langchain-ai/langchain/issues/4394
+        raise ValueError(
+            f"Loading templates with '{template_format}' format is no longer supported "
+            f"since it can lead to arbitrary code execution. Please migrate to using "
+            f"the 'f-string' template format, which does not suffer from this issue."
+        )
+
     return PromptTemplate(**config)


This affects all versions of package ffmpegdotjs. If attacker-controlled user input is given to the trimvideo function, it is possible for an attacker to execute arbitrary commands. This is due to use of the child_process exec function without input sanitization.

  trimvideo: (input, start, duration, output) => {
    return new Promise(function(resolve, reject) {
      if (fs.existsSync(input)) {
        exec(
          `ffmpeg -hide_banner -loglevel quiet -ss ${start} -i ${input} -t ${duration} -c copy -y ${output}.mp4`,
          (error, stdout, stderr) => {
            if (error) {
              reject(error);
              return;
            }
            resolve(`${output}.mp4`);
          }
        );
      } else {
        reject(new Error("ffmpegdotjs could not find file"));
      }
    });
  },

  trimvideo: (input, start, duration, output) => {
    return new Promise(function(resolve, reject) {
      if (fs.existsSync(input)) {
        execFile(
          'ffmpeg', `-hide_banner -loglevel quiet -ss ${start} -i ${input} -t ${duration} -c copy -y ${output}.mp4`.split(" "),
          (error, stdout, stderr) => {
            if (error) {
              reject(error);
              return;
            }
            resolve(`${output}.mp4`);
          }
        );
      } else {
        reject(new Error("ffmpegdotjs could not find file"));
      }
    });
  },

--- a/index.js
+++ b/index.js
@@ -1,8 +1,8 @@
   trimvideo: (input, start, duration, output) => {
     return new Promise(function(resolve, reject) {
       if (fs.existsSync(input)) {
-        exec(
-          `ffmpeg -hide_banner -loglevel quiet -ss ${start} -i ${input} -t ${duration} -c copy -y ${output}.mp4`,
+        execFile(
+          'ffmpeg', `-hide_banner -loglevel quiet -ss ${start} -i ${input} -t ${duration} -c copy -y ${output}.mp4`.split(" "),
           (error, stdout, stderr) => {
             if (error) {
               reject(error);

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

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


In util/session/sessionmanager.go in Argo CD before 1.8.4, tokens continue to work even when the user account is disabled.

Insufficient Session Expiration

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

func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
	// Parse takes the token string and a function for looking up the key. The latter is especially
	// useful if you use multiple keys for your application.  The standard is to use 'kid' in the
	// head of the token to identify which key to use, but the parsed token (head and claims) is provided
	// to the callback, providing flexibility.
	var claims jwt.MapClaims
	settings, err := mgr.settingsMgr.GetSettings()
	if err != nil {
		return nil, err
	}
	token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return settings.ServerSignature, nil
	})
	if err != nil {
		return nil, err
	}

	issuedAt, err := jwtutil.IssuedAtTime(claims)
	if err != nil {
		return nil, err
	}

	subject := jwtutil.StringField(claims, "sub")
	id := jwtutil.StringField(claims, "jti")

	if projName, role, ok := rbacpolicy.GetProjectRoleFromSubject(subject); ok {
		proj, err := mgr.projectsLister.Get(projName)
		if err != nil {
			return nil, err
		}
		_, _, err = proj.GetJWTToken(role, issuedAt.Unix(), id)
		if err != nil {
			return nil, err
		}

		return token.Claims, nil
	}

	account, err := mgr.settingsMgr.GetAccount(subject)
	if err != nil {
		return nil, err
	}

	if id := jwtutil.StringField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
		return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
	}

	if account.PasswordMtime != nil && issuedAt.Before(*account.PasswordMtime) {
		return nil, fmt.Errorf("Account password has changed since token issued")
	}
	return token.Claims, nil
}

func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, error) {
	// Parse takes the token string and a function for looking up the key. The latter is especially
	// useful if you use multiple keys for your application.  The standard is to use 'kid' in the
	// head of the token to identify which key to use, but the parsed token (head and claims) is provided
	// to the callback, providing flexibility.
	var claims jwt.MapClaims
	settings, err := mgr.settingsMgr.GetSettings()
	if err != nil {
		return nil, err
	}
	token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return settings.ServerSignature, nil
	})
	if err != nil {
		return nil, err
	}

	issuedAt, err := jwtutil.IssuedAtTime(claims)
	if err != nil {
		return nil, err
	}

	subject := jwtutil.StringField(claims, "sub")
	id := jwtutil.StringField(claims, "jti")

	if projName, role, ok := rbacpolicy.GetProjectRoleFromSubject(subject); ok {
		proj, err := mgr.projectsLister.Get(projName)
		if err != nil {
			return nil, err
		}
		_, _, err = proj.GetJWTToken(role, issuedAt.Unix(), id)
		if err != nil {
			return nil, err
		}

		return token.Claims, nil
	}

	account, err := mgr.settingsMgr.GetAccount(subject)
	if err != nil {
		return nil, err
	}

	if !account.Enabled {
		return nil, fmt.Errorf("account %s is disabled", subject)
	}

	if id := jwtutil.StringField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
		return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
	}

	if account.PasswordMtime != nil && issuedAt.Before(*account.PasswordMtime) {
		return nil, fmt.Errorf("Account password has changed since token issued")
	}
	return token.Claims, nil
}

--- a/util/session/sessionmanager.go
+++ b/util/session/sessionmanager.go
@@ -45,6 +45,10 @@
 		return nil, err
 	}

+	if !account.Enabled {
+		return nil, fmt.Errorf("account %s is disabled", subject)
+	}
+
 	if id := jwtutil.StringField(claims, "jti"); id != "" && account.TokenIndex(id) == -1 {
 		return nil, fmt.Errorf("account %s does not have token with id %s", subject, id)
 	}

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

cd /workspace/argo-cd
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
cd ./util/session && go test -timeout 30s -run ^TestSessionManager_AdminToken_Deactivated$ github.com/argoproj/argo-cd/util/session


python-dbusmock before version 0.15.1 AddTemplate() D-Bus method call or DBusTestCase.spawn_server_template() method could be tricked into executing malicious code if an attacker supplies a .pyc file.

def load_module(name):
    if os.path.exists(name) and os.path.splitext(name)[1] == '.py':
        sys.path.insert(0, os.path.dirname(os.path.abspath(name)))
        try:
            m = os.path.splitext(os.path.basename(name))[0]
            module = importlib.import_module(m)
        finally:
            sys.path.pop(0)

        return module

def load_module(name):
    if os.path.exists(name) and os.path.splitext(name)[1] == '.py':
        mod = imp.new_module(os.path.splitext(os.path.basename(name))[0])
        with open(name) as f:
            exec(f.read(), mod.__dict__, mod.__dict__)
        return mod

    return importlib.import_module('dbusmock.templates.' + name)

--- a/dbusmock/mockobject.py
+++ /dev/null
@@ -1,10 +0,0 @@
-def load_module(name):
-    if os.path.exists(name) and os.path.splitext(name)[1] == '.py':
-        sys.path.insert(0, os.path.dirname(os.path.abspath(name)))
-        try:
-            m = os.path.splitext(os.path.basename(name))[0]
-            module = importlib.import_module(m)
-        finally:
-            sys.path.pop(0)
-
-        return module

--- /dev/null
+++ b/dbusmock/mockobject.py
@@ -0,0 +1,8 @@
+def load_module(name):
+    if os.path.exists(name) and os.path.splitext(name)[1] == '.py':
+        mod = imp.new_module(os.path.splitext(os.path.basename(name))[0])
+        with open(name) as f:
+            exec(f.read(), mod.__dict__, mod.__dict__)
+        return mod
+
+    return importlib.import_module('dbusmock.templates.' + name)

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

cd /workspace/python-dbusmock
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
/workspace/PoC_env/CVE-2015-1326/bin/python -m pytest tests/test_api.py::TestTemplates::test_local --disable-warnings

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

cd /workspace/python-dbusmock
git apply --whitespace=nowarn /workspace/fix.patch
/workspace/PoC_env/CVE-2015-1326/bin/python -m pytest tests/test_api.py --disable-warnings


Matrix Synapse before 0.28.1 is prone to a denial of service flaw where malicious events injected with depth = 2^63 - 1 render rooms unusable, related to federation/federation_base.py and handlers/message.py, as exploited in the wild in April 2018.

def event_from_pdu_json(pdu_json, outlier=False):
    """Construct a FrozenEvent from an event json received over federation

    Args:
        pdu_json (object): pdu as received over federation
        outlier (bool): True to mark this event as an outlier

    Returns:
        FrozenEvent

    Raises:
        SynapseError: if the pdu is missing required fields
    """
    # we could probably enforce a bunch of other fields here (room_id, sender,
    # origin, etc etc)
    assert_params_in_request(pdu_json, ('event_id', 'type'))
    event = FrozenEvent(
        pdu_json
    )

    event.internal_metadata.outlier = outlier

    return event

    def create_new_client_event(self, builder, requester=None,
                                prev_events_and_hashes=None):
        """Create a new event for a local client

        Args:
            builder (EventBuilder):

            requester (synapse.types.Requester|None):

            prev_events_and_hashes (list[(str, dict[str, str], int)]|None):
                the forward extremities to use as the prev_events for the
                new event. For each event, a tuple of (event_id, hashes, depth)
                where *hashes* is a map from algorithm to hash.

                If None, they will be requested from the database.

        Returns:
            Deferred[(synapse.events.EventBase, synapse.events.snapshot.EventContext)]
        """

        if prev_events_and_hashes is not None:
            assert len(prev_events_and_hashes) <= 10, \
                "Attempting to create an event with %i prev_events" % (
                    len(prev_events_and_hashes),
            )
        else:
            prev_events_and_hashes = \
                yield self.store.get_prev_events_for_room(builder.room_id)

        if prev_events_and_hashes:
            depth = max([d for _, _, d in prev_events_and_hashes]) + 1
        else:
            depth = 1

        prev_events = [
            (event_id, prev_hashes)
            for event_id, prev_hashes, _ in prev_events_and_hashes
        ]

        builder.prev_events = prev_events
        builder.depth = depth

        context = yield self.state.compute_event_context(builder)
        if requester:
            context.app_service = requester.app_service

        if builder.is_state():
            builder.prev_state = yield self.store.add_event_hashes(
                context.prev_state_events
            )

        yield self.auth.add_auth_events(builder, context)

        signing_key = self.hs.config.signing_key[0]
        add_hashes_and_signatures(
            builder, self.server_name, signing_key
        )

        event = builder.build()

        logger.debug(
            "Created event %s with state: %s",
            event.event_id, context.prev_state_ids,
        )

        defer.returnValue(
            (event, context,)
        )

def event_from_pdu_json(pdu_json, outlier=False):
    """Construct a FrozenEvent from an event json received over federation

    Args:
        pdu_json (object): pdu as received over federation
        outlier (bool): True to mark this event as an outlier

    Returns:
        FrozenEvent

    Raises:
        SynapseError: if the pdu is missing required fields or is otherwise
            not a valid matrix event
    """
    # we could probably enforce a bunch of other fields here (room_id, sender,
    # origin, etc etc)
    assert_params_in_request(pdu_json, ('event_id', 'type', 'depth'))

    depth = pdu_json['depth']
    if not isinstance(depth, six.integer_types):
        raise SynapseError(400, "Depth %r not an intger" % (depth, ),
                           Codes.BAD_JSON)

    if depth < 0:
        raise SynapseError(400, "Depth too small", Codes.BAD_JSON)
    elif depth > MAX_DEPTH:
        raise SynapseError(400, "Depth too large", Codes.BAD_JSON)

    event = FrozenEvent(
        pdu_json
    )

    event.internal_metadata.outlier = outlier

    return event

    def create_new_client_event(self, builder, requester=None,
                                prev_events_and_hashes=None):
        """Create a new event for a local client

        Args:
            builder (EventBuilder):

            requester (synapse.types.Requester|None):

            prev_events_and_hashes (list[(str, dict[str, str], int)]|None):
                the forward extremities to use as the prev_events for the
                new event. For each event, a tuple of (event_id, hashes, depth)
                where *hashes* is a map from algorithm to hash.

                If None, they will be requested from the database.

        Returns:
            Deferred[(synapse.events.EventBase, synapse.events.snapshot.EventContext)]
        """

        if prev_events_and_hashes is not None:
            assert len(prev_events_and_hashes) <= 10, \
                "Attempting to create an event with %i prev_events" % (
                    len(prev_events_and_hashes),
            )
        else:
            prev_events_and_hashes = \
                yield self.store.get_prev_events_for_room(builder.room_id)

        if prev_events_and_hashes:
            depth = max([d for _, _, d in prev_events_and_hashes]) + 1
            # we cap depth of generated events, to ensure that they are not
            # rejected by other servers (and so that they can be persisted in
            # the db)
            depth = min(depth, MAX_DEPTH)
        else:
            depth = 1

        prev_events = [
            (event_id, prev_hashes)
            for event_id, prev_hashes, _ in prev_events_and_hashes
        ]

        builder.prev_events = prev_events
        builder.depth = depth

        context = yield self.state.compute_event_context(builder)
        if requester:
            context.app_service = requester.app_service

        if builder.is_state():
            builder.prev_state = yield self.store.add_event_hashes(
                context.prev_state_events
            )

        yield self.auth.add_auth_events(builder, context)

        signing_key = self.hs.config.signing_key[0]
        add_hashes_and_signatures(
            builder, self.server_name, signing_key
        )

        event = builder.build()

        logger.debug(
            "Created event %s with state: %s",
            event.event_id, context.prev_state_ids,
        )

        defer.returnValue(
            (event, context,)
        )

--- a/synapse/federation/federation_base.py
+++ b/synapse/federation/federation_base.py
@@ -9,11 +9,23 @@
         FrozenEvent

     Raises:
-        SynapseError: if the pdu is missing required fields
+        SynapseError: if the pdu is missing required fields or is otherwise
+            not a valid matrix event
     """
     # we could probably enforce a bunch of other fields here (room_id, sender,
     # origin, etc etc)
-    assert_params_in_request(pdu_json, ('event_id', 'type'))
+    assert_params_in_request(pdu_json, ('event_id', 'type', 'depth'))
+
+    depth = pdu_json['depth']
+    if not isinstance(depth, six.integer_types):
+        raise SynapseError(400, "Depth %r not an intger" % (depth, ),
+                           Codes.BAD_JSON)
+
+    if depth < 0:
+        raise SynapseError(400, "Depth too small", Codes.BAD_JSON)
+    elif depth > MAX_DEPTH:
+        raise SynapseError(400, "Depth too large", Codes.BAD_JSON)
+
     event = FrozenEvent(
         pdu_json
     )

--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -29,6 +29,10 @@

         if prev_events_and_hashes:
             depth = max([d for _, _, d in prev_events_and_hashes]) + 1
+            # we cap depth of generated events, to ensure that they are not
+            # rejected by other servers (and so that they can be persisted in
+            # the db)
+            depth = min(depth, MAX_DEPTH)
         else:
             depth = 1


sequelize is an Object-relational mapping, or a middleman to convert things from Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server into usable data for NodeJS. Before version 1.7.0-alpha3, sequelize defaulted SQLite to use MySQL backslash escaping, even though SQLite uses Postgres escaping.

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.

SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
  if (val === undefined || val === null) {
    return 'NULL';
  }

  switch (typeof val) {
    case 'boolean': return (val) ? 'true' : 'false';
    case 'number': return val+'';
  }

  if (val instanceof Date) {
    val = SqlString.dateToString(val, timeZone || "Z");
  }

  if (Buffer.isBuffer(val)) {
    return SqlString.bufferToString(val);
  }

  if (Array.isArray(val)) {
    return SqlString.arrayToList(val, timeZone);
  }

  if (typeof val === 'object') {
    if (stringifyObjects) {
      val = val.toString();
    } else {
      return SqlString.objectToValues(val, timeZone);
    }
  }

  if (dialect == "postgres") {
    // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
    val = val.replace(/'/g, "''");
  } else {
    val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
      switch(s) {
        case "\0": return "\\0";
        case "\n": return "\\n";
        case "\r": return "\\r";
        case "\b": return "\\b";
        case "\t": return "\\t";
        case "\x1a": return "\\Z";
        default: return "\\"+s;
      }
    });
  }
  return "'"+val+"'";
};

SqlString.escape = function(val, stringifyObjects, timeZone, dialect) {
  if (val === undefined || val === null) {
    return 'NULL';
  }

  switch (typeof val) {
    case 'boolean': return (val) ? 'true' : 'false';
    case 'number': return val+'';
  }

  if (val instanceof Date) {
    val = SqlString.dateToString(val, timeZone || "Z");
  }

  if (Buffer.isBuffer(val)) {
    return SqlString.bufferToString(val);
  }

  if (Array.isArray(val)) {
    return SqlString.arrayToList(val, timeZone);
  }

  if (typeof val === 'object') {
    if (stringifyObjects) {
      val = val.toString();
    } else {
      return SqlString.objectToValues(val, timeZone);
    }
  }

  if (dialect === "postgres" || dialect === "sqlite") {
    // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
    // http://stackoverflow.com/q/603572/130598
    val = val.replace(/'/g, "''");
  } else {
    val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
      switch(s) {
        case "\0": return "\\0";
        case "\n": return "\\n";
        case "\r": return "\\r";
        case "\b": return "\\b";
        case "\t": return "\\t";
        case "\x1a": return "\\Z";
        default: return "\\"+s;
      }
    });
  }
  return "'"+val+"'";
};

--- a/lib/sql-string.js
+++ b/lib/sql-string.js
@@ -28,8 +28,9 @@
     }
   }

-  if (dialect == "postgres") {
+  if (dialect === "postgres" || dialect === "sqlite") {
     // http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
+    // http://stackoverflow.com/q/603572/130598
     val = val.replace(/'/g, "''");
   } else {
     val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {


pipenv is a Python development workflow tool. Starting with version 2018.10.9 and prior to version 2022.1.8, a flaw in pipenv's parsing of requirements files allows an attacker to insert a specially crafted string inside a comment anywhere within a requirements.txt file, which will cause victims who use pipenv to install the requirements file to download dependencies from a package index server controlled by the attacker. By embedding malicious code in packages served from their malicious index server, the attacker can trigger arbitrary remote code execution (RCE) on the victims' systems. If an attacker is able to hide a malicious `--index-url` option in a requirements file that a victim installs with pipenv, the attacker can embed arbitrary malicious code in packages served from their malicious index server that will be executed on the victim's host during installation (remote code execution/RCE). When pip installs from a source distribution, any code in the setup.py is executed by the install process. This issue is patched in version 2022.1.8. The GitHub Security Advisory contains more information about this vulnerability.

def import_requirements(project, r=None, dev=False):
    from pipenv.patched.notpip._vendor import requests as pip_requests
    from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement
    from pipenv.vendor.pip_shims.shims import parse_requirements

    # Parse requirements.txt file with Pip's parser.
    # Pip requires a `PipSession` which is a subclass of requests.Session.
    # Since we're not making any network calls, it's initialized to nothing.
    if r:
        assert os.path.isfile(r)
    # Default path, if none is provided.
    if r is None:
        r = project.requirements_location
    with open(r) as f:
        contents = f.read()
    indexes = []
    trusted_hosts = []
    # Find and add extra indexes.
    for line in contents.split("\n"):
        index, extra_index, trusted_host, _ = parse_indexes(line.strip(), strict=True)
        if index:
            indexes = [index]
        if extra_index:
            indexes.append(extra_index)
        if trusted_host:
            trusted_hosts.append(trusted_host)
    indexes = sorted(set(indexes))
    trusted_hosts = sorted(set(trusted_hosts))
    reqs = [install_req_from_parsed_requirement(f) for f in parse_requirements(r, session=pip_requests)]
    for package in reqs:
        if package.name not in BAD_PACKAGES:
            if package.link is not None:
                package_string = (
                    f"-e {package.link}"
                    if package.editable
                    else str(package.link)
                )
                project.add_package_to_pipfile(package_string, dev=dev)
            else:
                project.add_package_to_pipfile(str(package.req), dev=dev)
    for index in indexes:
        trusted = index in trusted_hosts
        project.add_index_to_pipfile(index, verify_ssl=trusted)
    project.recase_pipfile()

def import_requirements(project, r=None, dev=False):
    from pipenv.patched.notpip._vendor import requests as pip_requests
    from pipenv.patched.notpip._internal.req.constructors import install_req_from_parsed_requirement
    from pipenv.vendor.pip_shims.shims import parse_requirements

    # Parse requirements.txt file with Pip's parser.
    # Pip requires a `PipSession` which is a subclass of requests.Session.
    # Since we're not making any network calls, it's initialized to nothing.
    if r:
        assert os.path.isfile(r)
    # Default path, if none is provided.
    if r is None:
        r = project.requirements_location
    with open(r) as f:
        contents = f.read()
    indexes = []
    trusted_hosts = []
    # Find and add extra indexes.
    for line in contents.split("\n"):
        index, extra_index, trusted_host, _ = parse_indexes(line.strip(), strict=True)
        if index:
            indexes = [index]
        if extra_index:
            indexes.append(extra_index)
        if trusted_host:
            trusted_hosts.append(get_host_and_port(trusted_host))
    indexes = sorted(set(indexes))
    trusted_hosts = sorted(set(trusted_hosts))
    reqs = [install_req_from_parsed_requirement(f) for f in parse_requirements(r, session=pip_requests)]
    for package in reqs:
        if package.name not in BAD_PACKAGES:
            if package.link is not None:
                package_string = (
                    f"-e {package.link}"
                    if package.editable
                    else str(package.link)
                )
                project.add_package_to_pipfile(package_string, dev=dev)
            else:
                project.add_package_to_pipfile(str(package.req), dev=dev)
    for index in indexes:
        # don't require HTTPS for trusted hosts (see: https://pip.pypa.io/en/stable/cli/pip/#cmdoption-trusted-host)
        host_and_port = get_host_and_port(index)
        require_valid_https = not any((v in trusted_hosts for v in (
            host_and_port,
            host_and_port.partition(':')[0],  # also check if hostname without port is in trusted_hosts
        )))
        project.add_index_to_pipfile(index, verify_ssl=require_valid_https)
    project.recase_pipfile()

def get_host_and_port(url):
    """Get the host, or the host:port pair if port is explicitly included, for the given URL.

    Examples:
    >>> get_host_and_port('example.com')
    'example.com'
    >>> get_host_and_port('example.com:443')
    'example.com:443'
    >>> get_host_and_port('http://example.com')
    'example.com'
    >>> get_host_and_port('https://example.com/')
    'example.com'
    >>> get_host_and_port('https://example.com:8081')
    'example.com:8081'
    >>> get_host_and_port('ssh://example.com')
    'example.com'

    :param url: the URL string to parse
    :return: a string with the host:port pair if the URL includes port number explicitly; otherwise, returns host only
    """
    url = urllib3_util.parse_url(url)
    return '{}:{}'.format(url.host, url.port) if url.port else url.host

--- a/pipenv/core.py
+++ b/pipenv/core.py
@@ -23,7 +23,7 @@
         if extra_index:
             indexes.append(extra_index)
         if trusted_host:
-            trusted_hosts.append(trusted_host)
+            trusted_hosts.append(get_host_and_port(trusted_host))
     indexes = sorted(set(indexes))
     trusted_hosts = sorted(set(trusted_hosts))
     reqs = [install_req_from_parsed_requirement(f) for f in parse_requirements(r, session=pip_requests)]
@@ -39,6 +39,11 @@
             else:
                 project.add_package_to_pipfile(str(package.req), dev=dev)
     for index in indexes:
-        trusted = index in trusted_hosts
-        project.add_index_to_pipfile(index, verify_ssl=trusted)
+        # don't require HTTPS for trusted hosts (see: https://pip.pypa.io/en/stable/cli/pip/#cmdoption-trusted-host)
+        host_and_port = get_host_and_port(index)
+        require_valid_https = not any((v in trusted_hosts for v in (
+            host_and_port,
+            host_and_port.partition(':')[0],  # also check if hostname without port is in trusted_hosts
+        )))
+        project.add_index_to_pipfile(index, verify_ssl=require_valid_https)
     project.recase_pipfile()

--- /dev/null
+++ b/pipenv/core.py
@@ -0,0 +1,22 @@
+def get_host_and_port(url):
+    """Get the host, or the host:port pair if port is explicitly included, for the given URL.
+
+    Examples:
+    >>> get_host_and_port('example.com')
+    'example.com'
+    >>> get_host_and_port('example.com:443')
+    'example.com:443'
+    >>> get_host_and_port('http://example.com')
+    'example.com'
+    >>> get_host_and_port('https://example.com/')
+    'example.com'
+    >>> get_host_and_port('https://example.com:8081')
+    'example.com:8081'
+    >>> get_host_and_port('ssh://example.com')
+    'example.com'
+
+    :param url: the URL string to parse
+    :return: a string with the host:port pair if the URL includes port number explicitly; otherwise, returns host only
+    """
+    url = urllib3_util.parse_url(url)
+    return '{}:{}'.format(url.host, url.port) if url.port else url.host


A vulnerability was found in Pengu. It has been declared as problematic. Affected by this vulnerability is the function runApp of the file src/index.js. The manipulation leads to cross-site request forgery. The attack can be launched remotely. The name of the patch is aea66f12b8cdfc3c8c50ad6a9c89d8307e9d0a91. It is recommended to apply a patch to fix this issue. The associated identifier of this vulnerability is VDB-216475.

Cross-Site Request Forgery (CSRF)

The web application does not, or cannot, sufficiently verify whether a request was intentionally provided by the user who sent the request, which could have originated from an unauthorized actor.

	function originIsAllowed() {
		return true;
	}

	function originIsAllowed(origin) {
		const url = new URL(origin);
		return acceptedOrigins.includes(url.hostname);
	}

--- a/src/index.js
+++ b/src/index.js
@@ -1,3 +1,4 @@
-	function originIsAllowed() {
-		return true;
+	function originIsAllowed(origin) {
+		const url = new URL(origin);
+		return acceptedOrigins.includes(url.hostname);
 	}

--- /dev/null
+++ b/src/index.js
@@ -0,0 +1 @@
+const acceptedOrigins = process.env.ACCEPTED_ORIGINS ? process.env.ACCEPTED_ORIGINS.split(',') : ['localhost', '127.0.0.1'];


SudoBot, a Discord moderation bot, is vulnerable to privilege escalation and exploit of the `-config` command in versions prior to 9.26.7. Anyone is theoretically able to update any configuration of the bot and potentially gain control over the bot's settings. Every version of v9 before v9.26.7 is affected. Other versions (e.g. v8) are not affected. Users should upgrade to version 9.26.7 to receive a patch. A workaround would be to create a command permission overwrite in the Database. A SQL statement provided in the GitHub Security Advisor can be executed to create a overwrite that disallows users without `ManageGuild` permission to run the `-config` command. Run the SQL statement for every server the bot is in, and replace `<guild_id>` with the appropriate Guild ID each time.

Missing Authorization

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

class ConfigCommand extends Command {
    public override readonly name = "config";
    public override readonly description: string = "Manage configuration.";
    public override readonly defer = true;
    public override readonly systemPermissions = [];
    public override readonly usage = ["<subcommand: String> [...args: Any[]]"];
    public override readonly subcommandMeta: Record<string, SubcommandMeta> = {
        get: {
            description: "Get the value of a configuration key",
            usage: ["<key>"]
        },
        set: {
            description: "Set the value of a configuration key",
            usage: ["<key> <value>"]
        },
        save: {
            description: "Save the current configuration."
        },
        restore: {
            description: "Restore the previously saved configuration."
        },
        unset: {
            description: "Unset a configuration key"
        }
    };

    @Inject()
    private readonly configManager!: ConfigurationManager;
    @Inject()
    private readonly permissionManagerService!: PermissionManagerService;

    protected dottedConfig = {
        guild: getZodPropertyPaths(GuildConfigSchema),
        system: getZodPropertyPaths(SystemConfigSchema)
    };

    public override build(): Buildable[] {
        return [
            this.buildChatInput()
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("get")
                        .setDescription("Get the value of a configuration key")
                        .addStringOption(option =>
                            option
                                .setName("key")
                                .setDescription("The configuration key to view or change.")
                                .setAutocomplete(true)
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("config_type")
                                .setDescription("The configuration type")
                                .setChoices(
                                    {
                                        name: "Guild",
                                        value: "guild"
                                    },
                                    {
                                        name: "System",
                                        value: "system"
                                    }
                                )
                        )
                )
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("unset")
                        .setDescription("Unset a configuration key")
                        .addStringOption(option =>
                            option
                                .setName("key")
                                .setDescription("The configuration key to unset.")
                                .setAutocomplete(true)
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("config_type")
                                .setDescription("The configuration type")
                                .setChoices(
                                    {
                                        name: "Guild",
                                        value: "guild"
                                    },
                                    {
                                        name: "System",
                                        value: "system"
                                    }
                                )
                        )
                )
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("set")
                        .setDescription("Set the value of a configuration key")
                        .addStringOption(option =>
                            option
                                .setName("key")
                                .setDescription("The configuration key to view or change.")
                                .setAutocomplete(true)
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("value")
                                .setDescription("The new value to set the configuration key to.")
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("cast")
                                .setDescription("The type to cast the value to.")
                                .setChoices(
                                    {
                                        name: "String",
                                        value: "string"
                                    },
                                    {
                                        name: "Number",
                                        value: "number"
                                    },
                                    {
                                        name: "Boolean",
                                        value: "boolean"
                                    },
                                    {
                                        name: "JSON",
                                        value: "json"
                                    }
                                )
                        )
                        .addBooleanOption(option =>
                            option
                                .setName("save")
                                .setDescription("Save the current configuration immediately.")
                        )
                        .addBooleanOption(option =>
                            option
                                .setName("no_create")
                                .setDescription("Do not create the key if it does not exist.")
                        )
                        .addStringOption(option =>
                            option
                                .setName("config_type")
                                .setDescription("The configuration type")
                                .setChoices(
                                    {
                                        name: "Guild",
                                        value: "guild"
                                    },
                                    {
                                        name: "System",
                                        value: "system"
                                    }
                                )
                        )
                )
                .addSubcommand(subcommand =>
                    subcommand.setName("save").setDescription("Save the current configuration.")
                )
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("restore")
                        .setDescription("Restore the previously saved configuration.")
                )
        ];
    }

    public override async execute(
        context: LegacyContext | InteractionContext<ChatInputCommandInteraction>
    ): Promise<void> {
        const subcommand = context.isLegacy()
            ? context.argv[1]
            : context.options.getSubcommand(true);

        if (!subcommand) {
            await context.error("You must provide a subcommand.");
            return;
        }

        if (
            !context.isLegacy() &&
            (subcommand === "get" || subcommand === "set" || subcommand === "unset") &&
            context.options.getString("config_type") === "system" &&
            !(await this.permissionManagerService.isSystemAdmin(context.member!))
        ) {
            await context.error(
                "You do not have permission to view or change system configuration."
            );

            return;
        }

        switch (subcommand) {
            case "get":
                return this.get(context);
            case "set":
                return this.set(context);
            case "unset":
                return this.unset(context);
            case "save":
                return this.save(context);
            case "restore":
                return this.restore(context);
            default:
                await context.error(
                    `The subcommand \`${escapeInlineCode(
                        subcommand
                    )}\` does not exist. Please use one of the following subcommands: \`${this.subcommands.join(
                        "`, `"
                    )}\`.`
                );
                return;
        }
    }

    @GatewayEventListener("interactionCreate")
    public async onInteractionCreate(interaction: Interaction) {
        if (
            !interaction.isAutocomplete() ||
            interaction.commandName !== this.name ||
            !interaction.inGuild()
        ) {
            return;
        }

        const query = interaction.options.getFocused();
        const configType = (interaction.options.getString("config_type") ?? "guild") as
            | "guild"
            | "system";

        if (
            configType === "system" &&
            !(await this.permissionManagerService.isSystemAdmin(interaction.member as GuildMember))
        ) {
            await interaction.respond([]);
            return;
        }

        const config =
            configType === "guild"
                ? (this.dottedConfig?.guild ?? [])
                : (this.dottedConfig?.system ?? []);
        const keys = [];

        for (const key of config) {
            if (keys.length >= 25) {
                break;
            }

            if (key.includes(query)) {
                keys.push({ name: key, value: key });
            }
        }

        await interaction.respond(keys);
    }

    private async get(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        const key = context.isLegacy() ? context.args[1] : context.options.getString("key", true);

        if (!key) {
            await context.error("You must provide a configuration key to view.");
            return;
        }

        const configType = (
            context.isLegacy() ? "guild" : (context.options.getString("config_type") ?? "guild")
        ) as "guild" | "system";
        const config = configType === "guild" ? context.config : this.configManager.systemConfig;

        if (!config) {
            await context.error("No configuration exists for this server.");
            return;
        }

        if (!has(config, key)) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` does not exist.`
            );
            return;
        }

        const configValue = get(config, key);
        const embed = new EmbedBuilder()
            .setTitle("Configuration Value")
            .setDescription(
                `### ${inlineCode(key)}\n\n${codeBlock(
                    "json",
                    JSON5.stringify(configValue, {
                        space: 2,
                        replacer: null,
                        quote: '"'
                    })
                )}`
            )
            .setColor(Colors.Green)
            .setTimestamp();

        await context.replyEmbed(embed);
    }

    private async unset(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        const key = context.isLegacy() ? context.args[1] : context.options.getString("key", true);

        if (!key) {
            await context.error("You must provide a configuration key to unset.");
            return;
        }

        const configType = (
            context.isLegacy() ? "guild" : (context.options.getString("config_type") ?? "guild")
        ) as "guild" | "system";
        const config = configType === "guild" ? context.config : this.configManager.systemConfig;

        if (!config) {
            await context.error("No configuration exists for this server.");
            return;
        }

        if (!has(config, key)) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` does not exist.`
            );
            return;
        }

        try {
            unset(config, key);
        } catch (error) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` could not be unset: ${(error as Error)?.message}`
            );

            return;
        }

        const embed = new EmbedBuilder()
            .setTitle("Configuration Key Unset")
            .setDescription(`### ${inlineCode(key)}\n\nSuccessfully unset this key.`)
            .setColor(Colors.Green)
            .setTimestamp();

        await context.replyEmbed(embed);
    }

    private async set(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        if (context.isLegacy()) {
            if (!context.args[1]) {
                await context.error("You must provide a configuration key to set.");
                return;
            }

            if (!context.args[2]) {
                await context.error("You must provide a value to set the configuration key to.");
                return;
            }
        }

        const key = context.isLegacy() ? context.args[1] : context.options.getString("key", true);
        const value = context.isLegacy()
            ? context.commandContent
                  .slice(context.argv[0].length)
                  .trimStart()
                  .slice(context.argv[1].length)
                  .trimStart()
                  .slice(context.argv[2].length)
                  .trim()
            : context.options.getString("value", true);
        const cast = (
            context.isLegacy() ? "json" : (context.options.getString("cast") ?? "string")
        ) as CastType;
        const save = context.isLegacy() ? false : context.options.getBoolean("save");
        const noCreate = context.isLegacy() ? false : context.options.getBoolean("no_create");
        const configType = (
            context.isLegacy() ? "guild" : (context.options.getString("config_type") ?? "guild")
        ) as "guild" | "system";
        const config = configType === "guild" ? context.config : this.configManager.systemConfig;

        if (!config) {
            await context.error("No configuration exists for this server.");
            return;
        }

        if (!key) {
            await context.error("You must provide a configuration key to set.");
            return;
        }

        if (noCreate && !has(config, key)) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` does not exist.`
            );
            return;
        }

        let finalValue;

        switch (cast) {
            case "string":
                finalValue = value;
                break;
            case "number":
                finalValue = parseFloat(value);

                if (isNaN(finalValue)) {
                    await context.error(
                        `The value \`${escapeInlineCode(value)}\` is not a valid number.`
                    );
                    return;
                }

                break;
            case "boolean":
                {
                    const lowerCased = value.toLowerCase();

                    if (lowerCased !== "true" && lowerCased !== "false") {
                        await context.error(
                            `The value \`${escapeInlineCode(value)}\` is not a valid boolean.`
                        );
                        return;
                    }

                    finalValue = lowerCased === "true";
                }
                break;
            case "json":
                try {
                    finalValue = JSON5.parse(value);
                } catch (e) {
                    const error = codeBlock(
                        e instanceof Object && "message" in e ? `${e.message}` : `${e}`
                    );
                    await context.reply({
                        embeds: [
                            {
                                description: `### ${context.emoji(
                                    "error"
                                )} Failed to parse the value as JSON\n\n${codeBlock(error.slice(0, 1800))}${
                                    error.length > 1800
                                        ? "\n... The error message is loo long."
                                        : ""
                                }`,
                                color: Colors.Red,
                                footer: {
                                    text: "No changes were made to the configuration"
                                },
                                timestamp: new Date().toISOString()
                            }
                        ]
                    });

                    return;
                }

                break;
        }

        set(config, key, finalValue, {
            create: !noCreate
        });

        const embed = new EmbedBuilder();
        const error = this.configManager.testConfig();
        const errorString = error
            ? JSON5.stringify(error.error.format(), {
                  space: 2,
                  replacer: null,
                  quote: '"'
              })
            : null;

        if (errorString && error) {
            await this.configManager.load();

            embed
                .setDescription(
                    `### ${context.emoji("error")} The configuration is invalid (${inlineCode(
                        error.type
                    )})\n\nThe changes were not saved.\n\n${codeBlock(errorString.slice(0, 1800))}${
                        errorString.length > 1800 ? "\n... The error description is loo long." : ""
                    }`
                )
                .setColor(Colors.Red)
                .setFooter({ text: "The configuration was not saved." });

            await context.replyEmbed(embed);
            return;
        }

        embed
            .setTitle("Configuration Value Changed")
            .setDescription(
                `### ${inlineCode(key)}\n\n${codeBlock(
                    "json",
                    JSON5.stringify(finalValue, {
                        space: 2,
                        replacer: null,
                        quote: '"'
                    })
                )}`
            )
            .setColor(Colors.Green)
            .setTimestamp()
            .setFooter({ text: `The configuration was ${save ? "saved" : "applied"}.` });

        if (save) {
            await this.configManager.write({
                guild: configType === "guild",
                system: configType === "system"
            });
        }

        await context.replyEmbed(embed);
    }

    private async save(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        await this.configManager.write();
        await context.success("The configuration was saved.");
    }

    private async restore(
        context: LegacyContext | InteractionContext<ChatInputCommandInteraction>
    ) {
        await this.configManager.load();
        await context.success("The configuration was restored.");
    }
}

class ConfigCommand extends Command {
    public override readonly name = "config";
    public override readonly description: string = "Manage configuration.";
    public override readonly defer = true;
    public override readonly systemPermissions = [];
    public override readonly usage = ["<subcommand: String> [...args: Any[]]"];
    public override readonly subcommandMeta: Record<string, SubcommandMeta> = {
        get: {
            description: "Get the value of a configuration key",
            usage: ["<key>"]
        },
        set: {
            description: "Set the value of a configuration key",
            usage: ["<key> <value>"]
        },
        save: {
            description: "Save the current configuration."
        },
        restore: {
            description: "Restore the previously saved configuration."
        },
        unset: {
            description: "Unset a configuration key"
        }
    };
    public override readonly permissions = [PermissionFlags.ManageGuild];

    @Inject()
    private readonly configManager!: ConfigurationManager;
    @Inject()
    private readonly permissionManagerService!: PermissionManagerService;

    protected dottedConfig = {
        guild: getZodPropertyPaths(GuildConfigSchema),
        system: getZodPropertyPaths(SystemConfigSchema)
    };

    public override build(): Buildable[] {
        return [
            this.buildChatInput()
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("get")
                        .setDescription("Get the value of a configuration key")
                        .addStringOption(option =>
                            option
                                .setName("key")
                                .setDescription("The configuration key to view or change.")
                                .setAutocomplete(true)
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("config_type")
                                .setDescription("The configuration type")
                                .setChoices(
                                    {
                                        name: "Guild",
                                        value: "guild"
                                    },
                                    {
                                        name: "System",
                                        value: "system"
                                    }
                                )
                        )
                )
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("unset")
                        .setDescription("Unset a configuration key")
                        .addStringOption(option =>
                            option
                                .setName("key")
                                .setDescription("The configuration key to unset.")
                                .setAutocomplete(true)
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("config_type")
                                .setDescription("The configuration type")
                                .setChoices(
                                    {
                                        name: "Guild",
                                        value: "guild"
                                    },
                                    {
                                        name: "System",
                                        value: "system"
                                    }
                                )
                        )
                )
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("set")
                        .setDescription("Set the value of a configuration key")
                        .addStringOption(option =>
                            option
                                .setName("key")
                                .setDescription("The configuration key to view or change.")
                                .setAutocomplete(true)
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("value")
                                .setDescription("The new value to set the configuration key to.")
                                .setRequired(true)
                        )
                        .addStringOption(option =>
                            option
                                .setName("cast")
                                .setDescription("The type to cast the value to.")
                                .setChoices(
                                    {
                                        name: "String",
                                        value: "string"
                                    },
                                    {
                                        name: "Number",
                                        value: "number"
                                    },
                                    {
                                        name: "Boolean",
                                        value: "boolean"
                                    },
                                    {
                                        name: "JSON",
                                        value: "json"
                                    }
                                )
                        )
                        .addBooleanOption(option =>
                            option
                                .setName("save")
                                .setDescription("Save the current configuration immediately.")
                        )
                        .addBooleanOption(option =>
                            option
                                .setName("no_create")
                                .setDescription("Do not create the key if it does not exist.")
                        )
                        .addStringOption(option =>
                            option
                                .setName("config_type")
                                .setDescription("The configuration type")
                                .setChoices(
                                    {
                                        name: "Guild",
                                        value: "guild"
                                    },
                                    {
                                        name: "System",
                                        value: "system"
                                    }
                                )
                        )
                )
                .addSubcommand(subcommand =>
                    subcommand.setName("save").setDescription("Save the current configuration.")
                )
                .addSubcommand(subcommand =>
                    subcommand
                        .setName("restore")
                        .setDescription("Restore the previously saved configuration.")
                )
        ];
    }

    public override async execute(
        context: LegacyContext | InteractionContext<ChatInputCommandInteraction>
    ): Promise<void> {
        const subcommand = context.isLegacy()
            ? context.argv[1]
            : context.options.getSubcommand(true);

        if (!subcommand) {
            await context.error("You must provide a subcommand.");
            return;
        }

        if (
            !context.isLegacy() &&
            (subcommand === "get" || subcommand === "set" || subcommand === "unset") &&
            context.options.getString("config_type") === "system" &&
            !(await this.permissionManagerService.isSystemAdmin(context.member!))
        ) {
            await context.error(
                "You do not have permission to view or change system configuration."
            );

            return;
        }

        switch (subcommand) {
            case "get":
                return this.get(context);
            case "set":
                return this.set(context);
            case "unset":
                return this.unset(context);
            case "save":
                return this.save(context);
            case "restore":
                return this.restore(context);
            default:
                await context.error(
                    `The subcommand \`${escapeInlineCode(
                        subcommand
                    )}\` does not exist. Please use one of the following subcommands: \`${this.subcommands.join(
                        "`, `"
                    )}\`.`
                );
                return;
        }
    }

    @GatewayEventListener("interactionCreate")
    public async onInteractionCreate(interaction: Interaction) {
        if (
            !interaction.isAutocomplete() ||
            interaction.commandName !== this.name ||
            !interaction.inGuild()
        ) {
            return;
        }

        const query = interaction.options.getFocused();
        const configType = (interaction.options.getString("config_type") ?? "guild") as
            | "guild"
            | "system";

        if (
            configType === "system" &&
            !(await this.permissionManagerService.isSystemAdmin(interaction.member as GuildMember))
        ) {
            await interaction.respond([]);
            return;
        }

        const config =
            configType === "guild"
                ? (this.dottedConfig?.guild ?? [])
                : (this.dottedConfig?.system ?? []);
        const keys = [];

        for (const key of config) {
            if (keys.length >= 25) {
                break;
            }

            if (key.includes(query)) {
                keys.push({ name: key, value: key });
            }
        }

        await interaction.respond(keys);
    }

    private async get(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        const key = context.isLegacy() ? context.args[1] : context.options.getString("key", true);

        if (!key) {
            await context.error("You must provide a configuration key to view.");
            return;
        }

        const configType = (
            context.isLegacy() ? "guild" : (context.options.getString("config_type") ?? "guild")
        ) as "guild" | "system";
        const config = configType === "guild" ? context.config : this.configManager.systemConfig;

        if (!config) {
            await context.error("No configuration exists for this server.");
            return;
        }

        if (!has(config, key)) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` does not exist.`
            );
            return;
        }

        const configValue = get(config, key);
        const embed = new EmbedBuilder()
            .setTitle("Configuration Value")
            .setDescription(
                `### ${inlineCode(key)}\n\n${codeBlock(
                    "json",
                    JSON5.stringify(configValue, {
                        space: 2,
                        replacer: null,
                        quote: '"'
                    })
                )}`
            )
            .setColor(Colors.Green)
            .setTimestamp();

        await context.replyEmbed(embed);
    }

    private async unset(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        const key = context.isLegacy() ? context.args[1] : context.options.getString("key", true);

        if (!key) {
            await context.error("You must provide a configuration key to unset.");
            return;
        }

        const configType = (
            context.isLegacy() ? "guild" : (context.options.getString("config_type") ?? "guild")
        ) as "guild" | "system";
        const config = configType === "guild" ? context.config : this.configManager.systemConfig;

        if (!config) {
            await context.error("No configuration exists for this server.");
            return;
        }

        if (!has(config, key)) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` does not exist.`
            );
            return;
        }

        try {
            unset(config, key);
        } catch (error) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` could not be unset: ${(error as Error)?.message}`
            );

            return;
        }

        const embed = new EmbedBuilder()
            .setTitle("Configuration Key Unset")
            .setDescription(`### ${inlineCode(key)}\n\nSuccessfully unset this key.`)
            .setColor(Colors.Green)
            .setTimestamp();

        await context.replyEmbed(embed);
    }

    private async set(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        if (context.isLegacy()) {
            if (!context.args[1]) {
                await context.error("You must provide a configuration key to set.");
                return;
            }

            if (!context.args[2]) {
                await context.error("You must provide a value to set the configuration key to.");
                return;
            }
        }

        const key = context.isLegacy() ? context.args[1] : context.options.getString("key", true);
        const value = context.isLegacy()
            ? context.commandContent
                  .slice(context.argv[0].length)
                  .trimStart()
                  .slice(context.argv[1].length)
                  .trimStart()
                  .slice(context.argv[2].length)
                  .trim()
            : context.options.getString("value", true);
        const cast = (
            context.isLegacy() ? "json" : (context.options.getString("cast") ?? "string")
        ) as CastType;
        const save = context.isLegacy() ? false : context.options.getBoolean("save");
        const noCreate = context.isLegacy() ? false : context.options.getBoolean("no_create");
        const configType = (
            context.isLegacy() ? "guild" : (context.options.getString("config_type") ?? "guild")
        ) as "guild" | "system";
        const config = configType === "guild" ? context.config : this.configManager.systemConfig;

        if (!config) {
            await context.error("No configuration exists for this server.");
            return;
        }

        if (!key) {
            await context.error("You must provide a configuration key to set.");
            return;
        }

        if (noCreate && !has(config, key)) {
            await context.error(
                `The configuration key \`${escapeInlineCode(key)}\` does not exist.`
            );
            return;
        }

        let finalValue;

        switch (cast) {
            case "string":
                finalValue = value;
                break;
            case "number":
                finalValue = parseFloat(value);

                if (isNaN(finalValue)) {
                    await context.error(
                        `The value \`${escapeInlineCode(value)}\` is not a valid number.`
                    );
                    return;
                }

                break;
            case "boolean":
                {
                    const lowerCased = value.toLowerCase();

                    if (lowerCased !== "true" && lowerCased !== "false") {
                        await context.error(
                            `The value \`${escapeInlineCode(value)}\` is not a valid boolean.`
                        );
                        return;
                    }

                    finalValue = lowerCased === "true";
                }
                break;
            case "json":
                try {
                    finalValue = JSON5.parse(value);
                } catch (e) {
                    const error = codeBlock(
                        e instanceof Object && "message" in e ? `${e.message}` : `${e}`
                    );
                    await context.reply({
                        embeds: [
                            {
                                description: `### ${context.emoji(
                                    "error"
                                )} Failed to parse the value as JSON\n\n${codeBlock(error.slice(0, 1800))}${
                                    error.length > 1800
                                        ? "\n... The error message is loo long."
                                        : ""
                                }`,
                                color: Colors.Red,
                                footer: {
                                    text: "No changes were made to the configuration"
                                },
                                timestamp: new Date().toISOString()
                            }
                        ]
                    });

                    return;
                }

                break;
        }

        set(config, key, finalValue, {
            create: !noCreate
        });

        const embed = new EmbedBuilder();
        const error = this.configManager.testConfig();
        const errorString = error
            ? JSON5.stringify(error.error.format(), {
                  space: 2,
                  replacer: null,
                  quote: '"'
              })
            : null;

        if (errorString && error) {
            await this.configManager.load();

            embed
                .setDescription(
                    `### ${context.emoji("error")} The configuration is invalid (${inlineCode(
                        error.type
                    )})\n\nThe changes were not saved.\n\n${codeBlock(errorString.slice(0, 1800))}${
                        errorString.length > 1800 ? "\n... The error description is loo long." : ""
                    }`
                )
                .setColor(Colors.Red)
                .setFooter({ text: "The configuration was not saved." });

            await context.replyEmbed(embed);
            return;
        }

        embed
            .setTitle("Configuration Value Changed")
            .setDescription(
                `### ${inlineCode(key)}\n\n${codeBlock(
                    "json",
                    JSON5.stringify(finalValue, {
                        space: 2,
                        replacer: null,
                        quote: '"'
                    })
                )}`
            )
            .setColor(Colors.Green)
            .setTimestamp()
            .setFooter({ text: `The configuration was ${save ? "saved" : "applied"}.` });

        if (save) {
            await this.configManager.write({
                guild: configType === "guild",
                system: configType === "system"
            });
        }

        await context.replyEmbed(embed);
    }

    private async save(context: LegacyContext | InteractionContext<ChatInputCommandInteraction>) {
        await this.configManager.write();
        await context.success("The configuration was saved.");
    }

    private async restore(
        context: LegacyContext | InteractionContext<ChatInputCommandInteraction>
    ) {
        await this.configManager.load();
        await context.success("The configuration was restored.");
    }
}

--- a/src/main/typescript/commands/settings/ConfigCommand.ts
+++ b/src/main/typescript/commands/settings/ConfigCommand.ts
@@ -23,6 +23,7 @@
             description: "Unset a configuration key"
         }
     };
+    public override readonly permissions = [PermissionFlags.ManageGuild];

     @Inject()
     private readonly configManager!: ConfigurationManager;


Bostr is an nostr relay aggregator proxy that acts like a regular nostr relay. bostr let everyone in even having authorized_keys being set when noscraper is set to true. This vulnerability is fixed in 3.0.10.

module.exports = (authKey, data, ws, req) => {
  if (!authorized_keys?.length && !Object.keys(private_keys).length && !noscraper) return; // do nothing
  if (!validateEvent(data) || !verifyEvent(data)) {
    ws.send(JSON.stringify(["NOTICE", "error: invalid challenge response."]));
    return false;
  }

  if (!authorized_keys?.includes(data.pubkey) && !private_keys[data.pubkey] && !noscraper) {
    ws.send(JSON.stringify(["OK", data.id, false, "unauthorized."]));
    return false;
  }

  if (data.kind != 22242) {
    ws.send(JSON.stringify(["OK", data.id, false, "not kind 22242."]));
    return false;
  }

  const tags = Object.fromEntries(data.tags);

  if (!tags.relay?.includes(req.headers.host)) {
    ws.send(JSON.stringify(["OK", data.id, false, "unmatched relay url."]));
    return false;
  };

  if (tags.challenge !== authKey) {
    ws.send(JSON.stringify(["OK", data.id, false, "unmatched challenge string."]));
    return false;
  }

  ws.send(JSON.stringify(["OK", data.id, true, `Hello ${data.pubkey}`]));
  return true;
}

module.exports = (authKey, data, ws, req) => {
  if (!authorized_keys?.length && !Object.keys(private_keys).length && !noscraper) return; // do nothing
  if (!validateEvent(data) || !verifyEvent(data)) {
    ws.send(JSON.stringify(["NOTICE", "error: invalid challenge response."]));
    return false;
  }

  let pubkeyInConfig = authorized_keys?.includes(data.pubkey) || data.pubkey in private_keys;

  if (authorized_keys?.length && !pubkeyInConfig) {
    ws.send(JSON.stringify(["OK", data.id, false, "unauthorized."]));
    return false;
  }

  if (data.kind != 22242) {
    ws.send(JSON.stringify(["OK", data.id, false, "not kind 22242."]));
    return false;
  }

  const tags = Object.fromEntries(data.tags);

  if (!tags.relay?.includes(req.headers.host)) {
    ws.send(JSON.stringify(["OK", data.id, false, "unmatched relay url."]));
    return false;
  };

  if (tags.challenge !== authKey) {
    ws.send(JSON.stringify(["OK", data.id, false, "unmatched challenge string."]));
    return false;
  }

  ws.send(JSON.stringify(["OK", data.id, true, `Hello ${data.pubkey}`]));
  return true;
}

--- a/auth.js
+++ b/auth.js
@@ -5,7 +5,9 @@
     return false;
   }

-  if (!authorized_keys?.includes(data.pubkey) && !private_keys[data.pubkey] && !noscraper) {
+  let pubkeyInConfig = authorized_keys?.includes(data.pubkey) || data.pubkey in private_keys;
+
+  if (authorized_keys?.length && !pubkeyInConfig) {
     ws.send(JSON.stringify(["OK", data.id, false, "unauthorized."]));
     return false;
   }