Skip to content

Instantly share code, notes, and snippets.

@colleirose
Last active March 24, 2024 08:22
Show Gist options
  • Save colleirose/a4ad5d210144946541ab4ecd6f98e0e7 to your computer and use it in GitHub Desktop.
Save colleirose/a4ad5d210144946541ab4ecd6f98e0e7 to your computer and use it in GitHub Desktop.

This implementation is currently a draft and will require further enhancements for security and usability. Ideas to help improve the implementation that will need to be investigated:

  • See if something like the Signal protocol would be better than PGP
  • Look into HMAC or something like that for verifying message integrity
  • Look into a way to have a unique Argon2id salt

Purpose

I am creating a MediaWiki extension that provides social features like Fandom has. The overall purpose of this extension is to add the main social features that Fandom is well-known for (Discussions, message boards, profile bios and pictures, etc.) as well as adding some new ideas and features that have been requested by Fandom users or thought up by me, one of these being private messages, similar to DMs on Discord.

Although confidential communications are not required for the purpose of managing a wiki, if these discussions weren't intended to be secret to some degree, they would take place through a public commuication channel. I do not want this extension to increase the severity of the impact of a wiki's data breach, so, as such, encryption will be a built-in feature. Of course, zero-trust zero-access end-to-end encryption is not possible in the browser. However, this should be enough to help keep user messages safe in the event of a data breach where an attacker doesn't obtain write access to the file system (they could mess up the client-side encryption if they were able to modify the extension).

Users may choose to opt out of the encryption system through their account settings as well as during their initial login, but if someone messages them, the sender will be informed during the compose message screen that the messages will be stored unencrypted and therefore can be read by anyone with access to the server database. As with all modules in this extension, sysadmins managing the wiki infrastructure can disable the private messages module of this extension through LocalSettings.php. Additionally, if they are willing to accept the risks, they can fully turn off encryption in LocalSettings as well.

Threat model

This is intended to protect against attackers who can snoop on but not modify network traffic and attackers with filesystem read access and/or database read/write access but not filesystem write access.

Implementation

Technologies used for this implementation

Encryption algorithms

  • AES
  • PGP

Hashing algorithms

  • Argon2id

Compression algorithms

  • Brotli

Implementation details

As a prerequisite, the wiki must be using HTTPS to make it harder to perform MITM attacks on encryption-related JavaScript, CSS, and HTML code.

On account creation or login, we load client-side JavaScript code from a URL with subresource integrity. The client-side code will store an Argon2id hash of their password into localStorage with ROseKm62XrUyYJww as a salt. It isn't possible to use a unique salt because if we were to store unique salts per user in the database, we couldn't check theier unique salt until we're logged in as them (unless you let any unauthenticated user access the salt of any user which woould NOT be secure), but we can't calculate the user's password once we're already logged in unless we had temporarily stored it plaintext in localStorage, which is even less secure. We also aren’t using the user’s normal bcrypt hash from MediaWiki because the user's bcrypt hash can be found by anyone with access to the database, therefore defeating the security benefits.

Once we are logged in, we use the user's private MediaWiki API editing tokens that the client can access to send a request to the server for information about whether the user has private message encryption enabled. If they don’t, {"encryptionConfigured": "false", "optedOutOfEncryption": false} is returned.

If a false response is returned for both encryptionConfigured and optedOutOfEncryption, the user is asked if they want to have encryption enabled, and if so, if they’d like to have encryption automatically configured using their account login details as the encryption key or if they’d like to set a custom encryption key. If they choose to not use encryption, this information is sent to the API and optedOutOfEncryption will return true from now on for the user unless they turn encryption back on from their user preferences.

If they choose to use a custom encryption key, inform them about password best practices, encourage the use of a password manager, and set specific password requirements (forbid anything in the rockyou list, password must be at least 10 characters long, must have a number, must have an uppercase character, and must have a special character). The specific outcomes of their choices will be sent to the API and can be changed from their account preferences in the future if they wish.

If they don’t have encryption set up, we submit the following to the API, generated from the client-side:

  • A plain text PGP public key
  • A PGP private key encrypted with AES-256 using the scrypt hash of the user's password as the encryption key if a custom encryption key is not set, or the encryption key chosen by the user if one is set (the encryption key is not provided to the server, only the encrypted result)

If they do have encryption set up, we receive something similar to the following response (note that the server automatically keeps track of when it received the key update, in UTC time):

{
  "encryptionConfigured": true,
  "optedOutOfEncryption": false,
  "usingCustomEncryptionKey": <true or false>,
  "privateKey": "<insert long AES encrypted string>",
  "publicKey": "<insert long plaintext string>",
  "keyLastUpdated": "<insert UTC time>"
}

After receiving the encrypted private key from the server, it will be decrypted and stored in localStorage, in addition to the plaintext public key we receive. Once this process is complete, the user’s password hash is removed from localStorage. This process should be completely as quickly as possible and the password data should not be stored on the client-side for more than a few seconds at most.

When someone wants to message the user, they get the user’s public key through a public API endpoint that shows the public keys and past expired public keys for a specific user (the user is able to manually reset their personal PGP keys from their account preferences if they want to but will be informed that will mean they can no longer decrypt past messages), as well as when the keys were added/expired. The sender's client then uses the private and public keys that are available in localStorage to perform PGP encryption and signing to message the recipient. An example of what this would look like, using two PGP public keys that I found on my laptop:

[
  {
    "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nComment: User-ID:	Open Whisper Systems <support@whispersystems.org>\nComment: Created:	4/5/17 12:48 PM\nComment: Type:	4,096-bit RSA\nComment: Usage:	Signing, Encryption, Certifying User-IDs\nComment: Fingerprint:	DBA36B5181D0C816F630E889D980A17457F6FB06\n\n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\nN+O606NOXLwcmq5KZL0g\n=zyVo\n-----END PGP PUBLIC KEY BLOCK-----",
    "isCurrentKey": true,
    "timeAddedToAccount": "2024-03-14T18:01:53Z"
  },
  {
    "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\nComment: User-ID:	<proton@srp.modulus>\nComment: Created:	11/30/18 3:45 PM\nComment: Type:	255-bit EdDSA\nComment: Usage:	Signing, Encryption, Certifying User-IDs\nComment: Fingerprint:	248097092B458509C508DAC0350585C4E9518F26\n\nmDMEXAHLgxYJKwYBBAHaRw8BAQdAFurWXXwjTemqjD7CXjXVyKf0of7n9CtmL8v9\nenkzggG0EnByb3RvbkBzcnAubW9kdWx1c4h3BBAWCgApBQJcAcuDBgsJBwgDAgkQ\nNQWFxOlRjyYEFQgKAgMWAgECGQECGwMCHgEAAPGRAP9sauJsW12UMnTQUZpsbJb5\n3d0Wv55mZIIiJL2XulpWPQD/V6NglBd96lZKBmInSXX/kXatSv+y0io+LR8i2+jV\n+Aa4OARcAcuDEgorBgEEAZdVAQUBAQdAeJHUz1c9+KfEkSIgcBRE3WuXC4oj5a2/\nU3oASExGDW4DAQgHiGEEGBYIABMFAlwBy4MJEDUFhcTpUY8mAhsMAAD/XQD8DxNI\n6E78meodQI+wLsrKLeHn32iLvUqJbVDhfWSUWO4BAMcm1u02t4VKw++ttECPt+HU\ngPUq5pqQWe5Q2cW4TMsE\n=b/RG\n-----END PGP PUBLIC KEY BLOCK-----",
    "isCurrentKey": false,
    "timeAddedToAccount": "2024-01-29T18:14:25Z"
  }
]

Messages received are decrypted client-side with the private key the recipient has. Displayed messages may contain any normal wikitext syntax. Obviously, they cannot contain arbitrary HTML. As message encryption and decryption is done on the client-side, only encrypted message details are sent to the server and very limited plain text data, specifically the PGP-encrypted message timestamp, PGP-encrypted message subject, PGP-encrypted message content, the plain text sender, and the plain text recipient. Ensure that PGP signing is used in addition to PGP encryption to ensure message integrity (not sure how much of an issue it would be to not do this but better safe than sorry).

Message history stored on the server’s database will note a SHA1 of the public key the message was sent with (the server can find public keys by querying the recipients in the database) along with the actual PGP-encrypted message compressed in brotli (SHA1 hashes and brotli compression are used to save disk space – the brotli data is decompressed before being sent to the client to avoid adding bloat to the JavaScript code).

If the SHA1 hash of the public key the message was sent with doesn’t match the SHA1 hash of the user’s latest public key as obtained from the API endpoint, the client is to show a dismisssable notice above the message(s) stating "This user has changed their password, leading to a change in their encryption keys. This usually does not indicate any issues, but if you are concerned about the possibility that this is due to an account compromise, contact them off-platform to inquire about it." Showing this notice on received messages can be disabled in user preferences. See lower down for the specifics of where this data is received.

Encryption of message subjects and the time messages are sent will be performed, as mentioned earlier. The database fields with the message time and message subject are PGP encrypted just the same the way message content is. However, encrypting the participants of a conversation and the amount of messages in a conversation isn't possible under our current implementation.

If we want to start a new conversation thread with someone, we submit the recipient's username and the first message in the conversation to the server, looking something like this:

{
  "recipient": "Username",
  "firstMessageInConversation": {
    "recipient": "Username",
    "message": "-----BEGIN PGP MESSAGE-----\n\nhF4DT2BZI5kcwM4SAQdA3PGvRYYGaLBX97k82g2V4klH5cVRFxV4Dw1B21+mJx0w\nIhFzhLqMWvRQBBt9A5HBv4AqqrVudPmhhh39uGHfiLobE1NZj3ZoYlDEhtO8u+Ln\n0rkB44bMVVAJJhAPmChNUMPXobPK037+/lbItR+eL9O3ln8dhihpjnUo5ooNKesy\nn/L7tyINNPsPv53ECLt9GXQaPhRE0ej616iakb74p6IalwWOmr9Tn4Vz/LpJyqCx\nytoYKtFe6dv7/ynRQU89zu19S8qyPF+lB3lSkVT6PeKXLW4rOQHGNrYsX455uRFA\n86tMnAUaG3OUgaNrhHKF4QuMCK75jbc0HEAJ6xy3T6Z1KMGGTGhB9Jh5+A==\n=y6U2\n-----END PGP MESSAGE-----",
    "messageTimestamp": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAljiC6b/9JKhxUARtnQQ22BTL28jBhzGeKxQmpm40mGgw\nasX8Pwn79yGhssCC8h9UTqUdtcqdMAxfWktuQCbl4D759RnE2gcC3R5phqHEbU83\n0sAFAes/3P8U/QwkCINK7wt373aW1cxv7rJ5IxeLez81ci1OCO5Au3TPIri/HiDK\ncTBG4HLIC96Gm6Qod6lp88Euyrn3GXn5eO3oWSmK/yKHBN8nq4pxae4JcrIhlerX\nAkXx+3ztca7ITNjSQiM/HDK3q7uuLF9MSSPAVwYNde1xEzt3uxQZ7o8viv5IOD1k\ni7EejzDumCTGG8qpQTQy36iS8aJG26COC9BDo4WnwxIMpEHqlRRPEtunMex+JeIY\nPUjlRz9S/DQ=\n=xPhs\n-----END PGP MESSAGE-----",
    "messageSubject": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAQ1aRlkTPNnmBubgEJvuimJrI7fq8i8de8/TCqecoBx0w\nKBk4+hVVrh1nSkh8q1vWDuyI4GGOHWcu4Xh88s1sysiaTOPTLjBOm9xLwoacyK3O\n0sADAQlbLq1af+l2WbJ2e3dHJbqqGIVxsWOZVA8LT9Drfawyv3UH+zVfQzq1LRy7\nxswDrEl6K0xUjDQP3YuL/yPe6Dzy3EziuVVj0Q7bBHh6C3knirBvR2xrV8HwVOtH\nepBveQYtT0VAfjXHdu3J4YOd7XquhUI7V9phvolq1xPBpwQlfrS9fq/IdB5LLjmi\nKMNveMXEoYSa5RMR2DCvxZC2ckda8uCzS+bkOn5oXFTUbWLg99opuS+Z7WghaT9U\nHiA3djca\n=5u9D\n-----END PGP MESSAGE-----"
  }
}

When the client requests a list of conversations, the server will send a list of all conversations with the authenticated user as the recipient, in this format (the conversation IDs are generated through bin2hex(random_bytes(25)) - also note that we use the same conversation ID for everything here, that would not actually be what happens in reality, but it's just to make the examples simpler):

[
  {
    "sender": "Username",
    "conversationId": "7797666ad3807b15517e83210087a4a54c1a53db57fecece86",
    "latestMessageTimestamp": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAljiC6b/9JKhxUARtnQQ22BTL28jBhzGeKxQmpm40mGgw\nasX8Pwn79yGhssCC8h9UTqUdtcqdMAxfWktuQCbl4D759RnE2gcC3R5phqHEbU83\n0sAFAes/3P8U/QwkCINK7wt373aW1cxv7rJ5IxeLez81ci1OCO5Au3TPIri/HiDK\ncTBG4HLIC96Gm6Qod6lp88Euyrn3GXn5eO3oWSmK/yKHBN8nq4pxae4JcrIhlerX\nAkXx+3ztca7ITNjSQiM/HDK3q7uuLF9MSSPAVwYNde1xEzt3uxQZ7o8viv5IOD1k\ni7EejzDumCTGG8qpQTQy36iS8aJG26COC9BDo4WnwxIMpEHqlRRPEtunMex+JeIY\nPUjlRz9S/DQ=\n=xPhs\n-----END PGP MESSAGE-----"
  },
  {
    "sender": "Username",
    "conversationId": "7797666ad3807b15517e83210087a4a54c1a53db57fecece86",
    "latestMessageTimestamp": "-----BEGIN PGP MESSAGE-----\n\nhF4DT2BZI5kcwM4SAQdA2z9cfSVM+6XQ1CKN9IfKQQdIO+tLXnDkiqDvJKpmN2ow\naWtbswpkjBqNz1hhrZvJonkIjNHjFuUTLr45y4+PqzQC0rqCNdIz+8o7eW2Vs73X\n0sACARdv0Uhfxgo0+EvaT7imPPI6J4LSmR8oFxUjsvnJaqtZ13DHhIjr7Knc4ORd\nIjG7hTFYOHke/b6tDQcbwmlaGMgKFwXpbcose7lJMCLJZOuKkQyMaTGKPBDBCLF2\nhNHUOmaySCtjfPpeNgS5R3Im+psY7du/8u3U9MxkQLC9Og2Z69DpOg3hM1xm/OxP\n56lqg0Um3x+WyE6S1YoAjUvi3bjya6CxKGh8St3k5yKa187wy0xtTbnOy6nHAF3D\nvwAyvHU=\n=F2xh\n-----END PGP MESSAGE-----"
  }
]

Sending a message into a conversation will look something like this:

{
  "recipient": "Username",
  "conversationId": "7797666ad3807b15517e83210087a4a54c1a53db57fecece86",
  "message": "-----BEGIN PGP MESSAGE-----\n\nhF4DT2BZI5kcwM4SAQdA3PGvRYYGaLBX97k82g2V4klH5cVRFxV4Dw1B21+mJx0w\nIhFzhLqMWvRQBBt9A5HBv4AqqrVudPmhhh39uGHfiLobE1NZj3ZoYlDEhtO8u+Ln\n0rkB44bMVVAJJhAPmChNUMPXobPK037+/lbItR+eL9O3ln8dhihpjnUo5ooNKesy\nn/L7tyINNPsPv53ECLt9GXQaPhRE0ej616iakb74p6IalwWOmr9Tn4Vz/LpJyqCx\nytoYKtFe6dv7/ynRQU89zu19S8qyPF+lB3lSkVT6PeKXLW4rOQHGNrYsX455uRFA\n86tMnAUaG3OUgaNrhHKF4QuMCK75jbc0HEAJ6xy3T6Z1KMGGTGhB9Jh5+A==\n=y6U2\n-----END PGP MESSAGE-----",
  "messageTimestamp": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAljiC6b/9JKhxUARtnQQ22BTL28jBhzGeKxQmpm40mGgw\nasX8Pwn79yGhssCC8h9UTqUdtcqdMAxfWktuQCbl4D759RnE2gcC3R5phqHEbU83\n0sAFAes/3P8U/QwkCINK7wt373aW1cxv7rJ5IxeLez81ci1OCO5Au3TPIri/HiDK\ncTBG4HLIC96Gm6Qod6lp88Euyrn3GXn5eO3oWSmK/yKHBN8nq4pxae4JcrIhlerX\nAkXx+3ztca7ITNjSQiM/HDK3q7uuLF9MSSPAVwYNde1xEzt3uxQZ7o8viv5IOD1k\ni7EejzDumCTGG8qpQTQy36iS8aJG26COC9BDo4WnwxIMpEHqlRRPEtunMex+JeIY\nPUjlRz9S/DQ=\n=xPhs\n-----END PGP MESSAGE-----",
  "messageSubject": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAQ1aRlkTPNnmBubgEJvuimJrI7fq8i8de8/TCqecoBx0w\nKBk4+hVVrh1nSkh8q1vWDuyI4GGOHWcu4Xh88s1sysiaTOPTLjBOm9xLwoacyK3O\n0sADAQlbLq1af+l2WbJ2e3dHJbqqGIVxsWOZVA8LT9Drfawyv3UH+zVfQzq1LRy7\nxswDrEl6K0xUjDQP3YuL/yPe6Dzy3EziuVVj0Q7bBHh6C3knirBvR2xrV8HwVOtH\nepBveQYtT0VAfjXHdu3J4YOd7XquhUI7V9phvolq1xPBpwQlfrS9fq/IdB5LLjmi\nKMNveMXEoYSa5RMR2DCvxZC2ckda8uCzS+bkOn5oXFTUbWLg99opuS+Z7WghaT9U\nHiA3djca\n=5u9D\n-----END PGP MESSAGE-----"
}

The client can request a specific conversation with the authenticated user as the recipient by sending the conversation ID. The result of that request will look like this (see below for what publicKeySendingMessage is):

{
  "conversationId": "7797666ad3807b15517e83210087a4a54c1a53db57fecece86",
  "sender": "Username",
  "conversationSubject": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAQ1aRlkTPNnmBubgEJvuimJrI7fq8i8de8/TCqecoBx0w\nKBk4+hVVrh1nSkh8q1vWDuyI4GGOHWcu4Xh88s1sysiaTOPTLjBOm9xLwoacyK3O\n0sADAQlbLq1af+l2WbJ2e3dHJbqqGIVxsWOZVA8LT9Drfawyv3UH+zVfQzq1LRy7\nxswDrEl6K0xUjDQP3YuL/yPe6Dzy3EziuVVj0Q7bBHh6C3knirBvR2xrV8HwVOtH\nepBveQYtT0VAfjXHdu3J4YOd7XquhUI7V9phvolq1xPBpwQlfrS9fq/IdB5LLjmi\nKMNveMXEoYSa5RMR2DCvxZC2ckda8uCzS+bkOn5oXFTUbWLg99opuS+Z7WghaT9U\nHiA3djca\n=5u9D\n-----END PGP MESSAGE-----",
  "messages": [
    {
      "messageTimestamp": "-----BEGIN PGP MESSAGE-----\nhF4DT2BZI5kcwM4SAQdAljiC6b/9JKhxUARtnQQ22BTL28jBhzGeKxQmpm40mGgw\nasX8Pwn79yGhssCC8h9UTqUdtcqdMAxfWktuQCbl4D759RnE2gcC3R5phqHEbU83\n0sAFAes/3P8U/QwkCINK7wt373aW1cxv7rJ5IxeLez81ci1OCO5Au3TPIri/HiDK\ncTBG4HLIC96Gm6Qod6lp88Euyrn3GXn5eO3oWSmK/yKHBN8nq4pxae4JcrIhlerX\nAkXx+3ztca7ITNjSQiM/HDK3q7uuLF9MSSPAVwYNde1xEzt3uxQZ7o8viv5IOD1k\ni7EejzDumCTGG8qpQTQy36iS8aJG26COC9BDo4WnwxIMpEHqlRRPEtunMex+JeIY\nPUjlRz9S/DQ=\n=xPhs\n-----END PGP MESSAGE-----",
      "message": "-----BEGIN PGP MESSAGE-----\n\nhF4DT2BZI5kcwM4SAQdA3PGvRYYGaLBX97k82g2V4klH5cVRFxV4Dw1B21+mJx0w\nIhFzhLqMWvRQBBt9A5HBv4AqqrVudPmhhh39uGHfiLobE1NZj3ZoYlDEhtO8u+Ln\n0rkB44bMVVAJJhAPmChNUMPXobPK037+/lbItR+eL9O3ln8dhihpjnUo5ooNKesy\nn/L7tyINNPsPv53ECLt9GXQaPhRE0ej616iakb74p6IalwWOmr9Tn4Vz/LpJyqCx\nytoYKtFe6dv7/ynRQU89zu19S8qyPF+lB3lSkVT6PeKXLW4rOQHGNrYsX455uRFA\n86tMnAUaG3OUgaNrhHKF4QuMCK75jbc0HEAJ6xy3T6Z1KMGGTGhB9Jh5+A==\n=y6U2\n-----END PGP MESSAGE-----",
      "publicKeySendingMessage": "0b8572efd26a8a0e24a6912eb2debd68"
    },
    {
      "messageTimestamp": "-----BEGIN PGP MESSAGE-----\n\nhF4DT2BZI5kcwM4SAQdA2z9cfSVM+6XQ1CKN9IfKQQdIO+tLXnDkiqDvJKpmN2ow\naWtbswpkjBqNz1hhrZvJonkIjNHjFuUTLr45y4+PqzQC0rqCNdIz+8o7eW2Vs73X\n0sACARdv0Uhfxgo0+EvaT7imPPI6J4LSmR8oFxUjsvnJaqtZ13DHhIjr7Knc4ORd\nIjG7hTFYOHke/b6tDQcbwmlaGMgKFwXpbcose7lJMCLJZOuKkQyMaTGKPBDBCLF2\nhNHUOmaySCtjfPpeNgS5R3Im+psY7du/8u3U9MxkQLC9Og2Z69DpOg3hM1xm/OxP\n56lqg0Um3x+WyE6S1YoAjUvi3bjya6CxKGh8St3k5yKa187wy0xtTbnOy6nHAF3D\nvwAyvHU=\n=F2xh\n-----END PGP MESSAGE-----",
      "message": "-----BEGIN PGP MESSAGE-----\n\nhF4DT2BZI5kcwM4SAQdA3PGvRYYGaLBX97k82g2V4klH5cVRFxV4Dw1B21+mJx0w\nIhFzhLqMWvRQBBt9A5HBv4AqqrVudPmhhh39uGHfiLobE1NZj3ZoYlDEhtO8u+Ln\n0rkB44bMVVAJJhAPmChNUMPXobPK037+/lbItR+eL9O3ln8dhihpjnUo5ooNKesy\nn/L7tyINNPsPv53ECLt9GXQaPhRE0ej616iakb74p6IalwWOmr9Tn4Vz/LpJyqCx\nytoYKtFe6dv7/ynRQU89zu19S8qyPF+lB3lSkVT6PeKXLW4rOQHGNrYsX455uRFA\n86tMnAUaG3OUgaNrhHKF4QuMCK75jbc0HEAJ6xy3T6Z1KMGGTGhB9Jh5+A==\n=y6U2\n-----END PGP MESSAGE-----",
      "publicKeySendingMessage": "3c8309e405d7768525d406bd25a5d1df6207412e"
    }
  ]
}

As mentioned earlier, publicKeySendingMessage is a SHA1 hash of the PGP public key of the sender at the time of the message being sent. If it does not match the latest PGP public key the sender is using, inform the recipient of this as described earlier.

This implementation currently only describes 1-on-1 messages. It could likely be expanded to group messages using similar principles, but that's not being done yet. File uploads will be looked into in the future but I am not sure how they could be practically implemented right now due to the difference between storing securely encrypted text in the database and storing securely encrypted files on the filesystem. These are future updates that will need to be managed in a secure and performant way and will be looked into as needed.

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