Skip to content

Add kotlin random samples#63

Open
amazon-pratik wants to merge 1 commit into
mainfrom
feature/add-kotlin-samples
Open

Add kotlin random samples#63
amazon-pratik wants to merge 1 commit into
mainfrom
feature/add-kotlin-samples

Conversation

@amazon-pratik
Copy link
Copy Markdown
Owner

Added 50 randomly selected kotlin code samples from the security dataset.

Added 50 randomly selected kotlin code samples from the security dataset.
@amazon-q-developer
Copy link
Copy Markdown

⏳ I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @amazon-pratik, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request adds a new collection of 50 Kotlin code samples, specifically curated from a security dataset. These samples, along with a descriptive README, are included to expand the existing test cases for security analysis and to aid in further development and validation of security rules.

Highlights

  • New Kotlin Samples Directory: A new top-level directory, kotlin/, has been introduced, containing a random_samples/ subdirectory to house a collection of Kotlin code samples.
  • Documentation for Samples: A README.md file has been added within the kotlin/ directory, providing a clear description of the contents, purpose, and total count (50 files) of the new Kotlin samples.
  • Security Dataset Samples: Fifty randomly selected Kotlin code samples from a security dataset have been added. These samples are intended for analysis and testing purposes, often featuring annotations related to various Common Weakness Enumerations (CWEs) and their defect status.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

val ctx = InitialContext(env)

// BAD: User input used in lookup
ctx.lookup(name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: Unvalidated user input is directly used in JNDI lookup, potentially leading to JNDI injection vulnerabilities. Implement proper input validation and sanitization for the 'name' parameter before using it in ctx.lookup(). Consider using a whitelist of allowed values or a strict pattern matching.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the JNDI injection vulnerability by implementing input validation before using the 'name' parameter in ctx.lookup(). The original unvalidated lookup has been removed, and a new isValidJndiName function is introduced to perform strict validation. The fix is incomplete as it requires the implementation of proper validation logic in the isValidJndiName function, which should use either a whitelist of allowed values or a strict pattern matching approach.

Suggested change
ctx.lookup(name)
env.put(Context.PROVIDER_URL, "rmi://trusted-server:1099")
val ctx = InitialContext(env)
// GOOD: The name is validated before being used in lookup
if (isValidJndiName(name)) {
ctx.lookup(name)
} else {
// Reject the request
throw IllegalArgumentException("Invalid JNDI name")
}
}
fun isValid(name: String): Boolean {
return true;
}
// TODO: Implement isValidJndiName function with proper validation logic
fun isValidJndiName(name: String): Boolean {
// Implement strict pattern matching or whitelist validation
return false
}
}
// {/fact}

val r: Runtime = Runtime.getRuntime()
// {fact rule=improper-neutralization-of-special-elements-used-in-an-os-command@v1.0 defects=1}
// ruleid: command-injection-formatted-runtime-call
r.loadLibrary(String.format("%s.dll", input))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: A potential code injection vulnerability has been detected where untrusted input is passed to a method that may execute arbitrary code. This could allow attackers to inject and execute malicious code within the application, potentially leading to unauthorized access or other security breaches. To mitigate this risk, ensure all user input is properly validated and sanitized before use. Consider using safer alternatives like parameterized methods or more controlled approaches for handling user input. Learn More - https://cwe.mitre.org/data/definitions/94.html.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remediation involves using ProcessBuilder for safer command execution and implementing a whitelist for allowed library names to prevent arbitrary code execution.

Suggested change
r.loadLibrary(String.format("%s.dll", input))
class Cls {
fun Cls(input: String) {
val r: Runtime = Runtime.getRuntime()
// Use ProcessBuilder to safely execute commands
ProcessBuilder("/bin/sh", "-c", "some_tool", input).start()
}
fun test1(input: String) {
val r: Runtime = Runtime.getRuntime()
// Use a whitelist of allowed library names
val allowedLibraries = listOf("lib1", "lib2", "lib3")
if (allowedLibraries.contains(input)) {
r.loadLibrary("$input.dll")
} else {
throw IllegalArgumentException("Invalid library name")
}
}
fun test2(input: String) {
val r: Runtime = Runtime.getRuntime()
// This is already safe as it doesn't use user input
r.exec("echo 'blah'")
}
}

fun configureWebView(webView: WebView) {
// {fact rule=reflected-cross-site-scripting@v1.0 defects=1}
webView.settings.allowContentAccess = true // Sensitive
webView.settings.allowFileAccess = true // Sensitive
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: Enabling file and content access for WebView without proper security measures can lead to security vulnerabilities. Disable file and content access unless absolutely necessary. If required, implement strict security measures and content restrictions.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the security vulnerability by disabling file and content access for the WebView. The allowContentAccess and allowFileAccess settings are set to false, which prevents potential security risks associated with allowing access to local files and content. This change aligns with the recommendation to disable these features unless absolutely necessary, reducing the attack surface for potential cross-site scripting vulnerabilities.

Suggested change
webView.settings.allowFileAccess = true // Sensitive
class EnableFileAccess_1 {
fun configureWebView(webView: WebView) {
// Disable file and content access for security
webView.settings.allowContentAccess = false
webView.settings.allowFileAccess = false
}
}

// Noncompliant: The `httponly` attribute of cookies is set to `false`
fun noncompliant(value: String, response: HttpServletResponse) {
val cookie: Cookie = Cookie("cookie", value)
cookie.setHttpOnly(false)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: The httpOnly flag is explicitly set to false, which can expose the cookie to client-side script access, potentially leading to security vulnerabilities such as cross-site scripting (XSS) attacks. Set the httpOnly flag to true to enhance security by preventing client-side access to the cookie.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the security vulnerability by changing the setHttpOnly flag from false to true. This enhancement prevents client-side scripts from accessing the cookie, reducing the risk of cross-site scripting (XSS) attacks. The function name "noncompliant" should ideally be changed to reflect the new compliant behavior, but this wasn't included in the fix to minimize changes outside the specific vulnerability.

Suggested change
cookie.setHttpOnly(false)
*/
// {fact rule=sensitive-cookie-without-http-only-flag@v1.0 defects=1}
// Compliant: The `httponly` attribute of cookies is set to `true`
fun noncompliant(value: String, response: HttpServletResponse) {
val cookie: Cookie = Cookie("cookie", value)
cookie.setHttpOnly(true)
response.addCookie(cookie)
}
// {/fact}

val r: Runtime = Runtime.getRuntime()
// {fact rule=improper-neutralization-of-special-elements-used-in-an-os-command@v1.0 defects=1}
// ruleid: command-injection-formatted-runtime-call
r.exec("/bin/sh -c some_tool" + input)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: Potential command injection vulnerability in r.exec() and r.loadLibrary() calls due to unsanitized user input. Sanitize and validate the 'input' parameter before using it in command execution or library loading.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the potential command injection vulnerabilities by commenting out the unsafe code in the Cls() and test1() functions and adding TODO comments to implement input sanitization and validation. This is an incomplete fix as it requires further implementation of proper input sanitization and validation methods before using user input in exec() and loadLibrary() calls. To complete the fix, developers should implement robust input validation techniques, such as whitelisting allowed characters or using prepared statements, to ensure that user input cannot be used to inject malicious commands.

Suggested change
r.exec("/bin/sh -c some_tool" + input)
class Cls {
fun Cls(input: String) {
val r: Runtime = Runtime.getRuntime()
// {fact rule=improper-neutralization-of-special-elements-used-in-an-os-command@v1.0 defects=1}
// ruleid: command-injection-formatted-runtime-call
// TODO: Implement input sanitization and validation before using in exec()
// r.exec("/bin/sh -c some_tool" + input)
// {/fact}
}
fun test1(input: String) {
val r: Runtime = Runtime.getRuntime()
// {fact rule=improper-neutralization-of-special-elements-used-in-an-os-command@v1.0 defects=1}
// ruleid: command-injection-formatted-runtime-call
// TODO: Implement input sanitization and validation before using in loadLibrary()
// r.loadLibrary(String.format("%s.dll", input))
// {/fact}
}
fun test2(input: String) {
val r: Runtime = Runtime.getRuntime()

// BAD: a request parameter is written directly to an error response page
response.sendError(
HttpServletResponse.SC_NOT_FOUND,
"The page \"" + request.getParameter("page") + "\" was not found.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: Unvalidated user input from request.getParameter("page") is directly used in the error response, potentially leading to XSS vulnerabilities. Sanitize or escape the user input before including it in the error message.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the XSS vulnerability by using StringEscapeUtils.escapeHtml4() from Apache Commons Text to sanitize the user input before including it in the error message. This prevents potential malicious scripts from being executed in the browser when the error message is displayed.

Suggested change
"The page \"" + request.getParameter("page") + "\" was not found.")
import jakarta.servlet.http.HttpServletResponse
import java.io.IOException
import kotlin.Throws
import org.apache.commons.text.StringEscapeUtils // Import for HTML escaping
class ExternalAPISinkExample : HttpServlet() {
@Throws(ServletException::class, IOException::class)
override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
// Sanitize user input to prevent XSS
val sanitizedPage = StringEscapeUtils.escapeHtml4(request.getParameter("page"))
response.sendError(
HttpServletResponse.SC_NOT_FOUND,
"The page \"$sanitizedPage\" was not found.")
}
}
// {/fact}

import java.io.OutputStream

// Assume this function is defined elsewhere in your codebase
fun retrievePassword(): String {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: Potential hardcoded credential detected. This code may contain
sensitive data such as passwords or API keys embedded directly in the
source. Hardcoded credentials can be extracted and misused, leading to
unauthorized access to systems or data breaches. To remediate this, store
secrets in environment variables or use a secrets management tool like AWS
Secrets Manager, Azure Key Vault, or HashiCorp Vault. Avoid committing
credentials to version control. For best practices, refer to -
https://cwe.mitre.org/data/definitions/798.html

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix replaces the hardcoded password retrieval with a method that securely retrieves the password from an environment variable. This approach eliminates the risk of exposing sensitive credentials in the source code.

Suggested change
fun retrievePassword(): String {
// Assume this function is defined elsewhere in your codebase
fun retrievePassword(): String {
// Use a secure method to retrieve the password, e.g., from environment variables or a secrets manager
return System.getenv("APP_PASSWORD") ?: throw IllegalStateException("Password not set in environment")
}
class PasswordWriter(private val outputStream: OutputStream) {
fun writePassword() {
val params = "password=${retrievePassword()}"
val writer = OutputStreamWriter(outputStream)
writer.write(params)
writer.flush()
writer.close() // Always close the writer
}
}

class ZipSlipBad {
@Throws(FileNotFoundException::class)
fun writeZipEntry(entry: ZipEntry, destinationDir: File?) {
val file = File(destinationDir, entry.name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: The code is vulnerable to a Zip Slip attack due to lack of path validation when extracting zip entries. Implement path traversal checks to ensure the extracted file path is within the intended destination directory.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the Zip Slip vulnerability by implementing path traversal checks. It normalizes the destination directory path and the entry's path, then verifies that the resulting file path is within the intended destination directory. If the path is valid, it proceeds with creating the file; otherwise, it throws a SecurityException. This ensures that malicious zip entries cannot write files outside the target directory.

Suggested change
val file = File(destinationDir, entry.name)
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.util.zip.ZipEntry
import java.nio.file.Path // import for Path operations
import kotlin.io.path.Path // import for Path constructor
class ZipSlipBad {
@Throws(FileNotFoundException::class)
fun writeZipEntry(entry: ZipEntry, destinationDir: File?) {
val destPath = destinationDir?.toPath()?.normalize()
val filePath = destPath?.resolve(entry.name)?.normalize()
if (filePath != null && filePath.startsWith(destPath)) {
val file = filePath.toFile()
val fos = FileOutputStream(file)
// ... write entry to fos ...
} else {
throw SecurityException("Entry is outside of the target directory")
}
}
}
// {/fact}

val dn = "OU=People,O=$organizationName"

// BAD: User input used in search filter without encoding
val filter = "username=$username"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: The ldapQueryBad function uses unencoded user input in LDAP queries, potentially allowing LDAP injection attacks. Encode user input before using it in LDAP queries, as demonstrated in the ldapQueryGood function.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the LDAP injection vulnerability in the ldapQueryBad function by encoding user input before using it in LDAP queries. The organizationName is now encoded using encoder.encodeForDN() before being used in the DN, and the username is encoded using encoder.encodeForLDAP() before being used in the search filter. This prevents potential LDAP injection attacks by ensuring that user input is properly sanitized before being used in LDAP operations.

Suggested change
val filter = "username=$username"
val organizationName: String = request.getParameter("organization_name")
val username: String = request.getParameter("username")
// GOOD: Organization name is encoded before being used in DN
val encoder: Encoder = DefaultEncoder.getInstance()
val safeOrganizationName: String = encoder.encodeForDN(organizationName)
val dn = "OU=People,O=$safeOrganizationName"
// GOOD: User input is encoded before being used in search filter
val safeUsername: String = encoder.encodeForLDAP(username)
val filter = "username=$safeUsername"
ctx.search(dn, filter, SearchControls())
}

protected override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
// BAD: a request parameter is written directly to the Servlet response stream
response.writer.print(
"The page \"" + request.getParameter("page") + "\" was not found.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Description: Unescaped user input is directly written to the response, potentially leading to Cross-Site Scripting (XSS) vulnerabilities. Sanitize or escape the user input before writing it to the response. Consider using a library like OWASP Java Encoder Project for proper output encoding.

Severity: Critical

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the Cross-Site Scripting (XSS) vulnerability by using the OWASP Java Encoder Project to properly encode the user input before writing it to the response. The Encode.forHtml() method is used to sanitize the 'page' parameter, preventing potential XSS attacks by encoding special characters that could be interpreted as HTML or JavaScript.

Suggested change
"The page \"" + request.getParameter("page") + "\" was not found.")
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import java.io.IOException
import org.owasp.encoder.Encode // Import OWASP Encoder for output encoding
// {fact rule=use-of-hard-coded-credentials@v1.0 defects=1}
class XSS : HttpServlet() {
@Throws(ServletException::class, IOException::class)
protected override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
// GOOD: User input is properly encoded before being written to the response
response.writer.print(
"The page \"" + Encode.forHtml(request.getParameter("page")) + "\" was not found.")
}
}
// {/fact}

@amazon-q-developer
Copy link
Copy Markdown

✅ I finished the code review, and left comments with the issues I found. I will now generate code fix suggestions.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds 50 randomly selected Kotlin code samples from a security dataset. Several code samples have security vulnerabilities, such as command injection, path traversal, XSS, Zip Slip, and insecure cookie settings. The fact rules in some samples do not accurately reflect the vulnerability. Some 'compliant' samples are misleading or incorrect.

print("Enter your filename:")
val filename = readLine()

val file = File(filename)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The code is vulnerable to path traversal. The filename is used to create a File object without proper validation, which allows accessing arbitrary files on the system. Sanitize the filename before creating the File object.

val r: Runtime = Runtime.getRuntime()
// {fact rule=improper-neutralization-of-special-elements-used-in-an-os-command@v1.0 defects=1}
// ruleid: command-injection-formatted-runtime-call
r.exec("/bin/sh -c some_tool" + input)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The code is vulnerable to command injection. The input string is concatenated with the command without proper sanitization, which allows executing arbitrary commands. Sanitize the input before executing the command.

@Throws(FileNotFoundException::class)
fun writeZipEntry(entry: ZipEntry, destinationDir: File?) {
val file = File(destinationDir, entry.name)
val fos = FileOutputStream(file) // BAD
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The code is vulnerable to Zip Slip. The entry.name is concatenated with the destinationDir without proper validation, which allows writing files outside the intended directory. Sanitize the entry.name before creating the File object.

Comment on lines +12 to +14
response.writer.print(
"The page \"" + request.getParameter("page") + "\" was not found.")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The request parameter is written directly to the Servlet response stream, which is vulnerable to cross-site scripting (XSS). Consider encoding the parameter before writing it to the response.

Comment on lines +14 to +15
// BAD: read from a file without checking its path
val fileReader = BufferedReader(FileReader(filename))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The code reads from a file without checking its path, which is vulnerable to path traversal. A malicious user could provide a path to a sensitive file outside the intended directory. Sanitize the filename before creating the FileReader.

import jakarta.servlet.http.HttpServletResponse
import java.lang.ProcessBuilder

// {fact rule=use-of-rsa-algorithm-without-oaep@v1.0 defects=1}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The fact rule use-of-rsa-algorithm-without-oaep does not accurately reflect the vulnerability in this code, which is command injection via environment variables. The rule should be updated to reflect the actual vulnerability.

// Noncompliant: The `httponly` attribute of cookies is set to `false`
fun noncompliant(value: String, response: HttpServletResponse) {
val cookie: Cookie = Cookie("cookie", value)
cookie.setHttpOnly(false)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The httponly attribute of the cookie is set to false, which makes the cookie accessible to JavaScript. This is a security risk, as a malicious script could steal the cookie. Set the httponly attribute to true to prevent this.

Comment on lines +23 to +25
System.out.format(cardSecurityCode +
" is not the right value. Hint: the card expires in %1\$ty.",
expirationDate)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The user-provided value cardSecurityCode is included in the format string, which is vulnerable to format string injection. A malicious user could provide an extra format specifier, which causes an exception to be thrown, or they could provide a %1$tm or %1$te format specifier to access the month or day of the expiration date. Use %s to include the user-provided cardSecurityCode in the output.

Suggested change
System.out.format(cardSecurityCode +
" is not the right value. Hint: the card expires in %1\$ty.",
expirationDate)
System.out.format("%s is not the right value. Hint: the card expires in %2$ty.",
cardSecurityCode,
expirationDate)

scheduler: Handler?,
flags: Int
) {
context.registerReceiver(receiver, filter) // Sensitive
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The intent receiver method is registered without specifying any broadcast permission. This allows any application to send broadcasts to the receiver, which could lead to security vulnerabilities. Specify a broadcast permission when registering the receiver.


var stringBuilder: StringBuilder = StringBuilder()
for (b in resultBytes) {
stringBuilder.append(Integer.toHexString(b and 0xFF))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using Integer.toHexString() creates a weak hash. It should be converted to BigInteger and then to a hex string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant