diff --git a/ChangeLog b/ChangeLog
index 2b9746bc..94acc106 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,107 @@
+2026-04-26 10:45:26 +0800  Kevin J. McCarthy  <kevin@8t8.us> (4a9c9248)
+
+        * Update UPDATING file for 2.3.2 release.
+
+M	UPDATING
+
+2026-04-18 22:08:19 +0800  Kevin J. McCarthy  <kevin@8t8.us> (834c5a2e)
+
+        * Fix IMAP auth_cram MD5 digest of secret to use memcpy().
+        
+        For a secret longer than MD5_BLOCK_LEN, an MD5 digest is used instead.
+        However, mutt was incorrectly using strfcpy() instead of memcpy() on
+        the raw binary value returned by md5_buffer in hash_passwd.  If
+        hash_passwd contained an '\0' it would result in the value being
+        truncated.
+        
+        Additionally, the strfcpy was truncating the hash_passwd by one byte
+        regardless, due to passing a "size" of MD5_DIGEST_LEN when the data
+        itself was length MD5_DIGEST_LEN.
+        
+        This likely hasn't been a reported issue because:
+        1. CRAM-MD5 is not used much anymore
+        2. Most people likely don't have a password length greater than 64
+           bytes.
+        
+        Thanks to evilrabbit@tutamail.com for the security report.
+
+M	imap/auth_cram.c
+
+2026-04-18 22:40:46 +0800  Kevin J. McCarthy  <kevin@8t8.us> (12f54fe3)
+
+        * Check for embedded nul in url_pct_decode().
+        
+        Consider %00 an invalid character in a URL.
+        
+        Thanks to evilrabbit@tutamail.com for the security report.
+        
+        Reviewed-by: Alejandro Colomar <alx@kernel.org>
+
+M	url.c
+
+2026-04-18 22:36:37 +0800  Kevin J. McCarthy  <kevin@8t8.us> (f547a849)
+
+        * Fix imap_auth_gss() security level size check and buf_size type.
+        
+        Make sure send_token.length is 4 bytes before reading the data.
+        
+        Fix the buf_size type to be uint32_t instead of long.  ntohl()
+        operates on, and returns, a 32 bit unsigned integer.  Most
+        architectures now use a 64-bit long.
+        
+        I believe this only worked because in Little-Endian, the
+        least-significant bits come first, so even though we were using 8
+        bytes of send_token.value (4 of which were out of bounds) for the cast
+        to long, only the first 4 bytes were used to truncate to the uint32_t
+        that ntohl() used.  Likewise when we converted htonl() further down.
+        
+        Additionally, the comments indicate that mutt wasn't using buf_size in
+        any case, so perhaps that also explains the lack of bug reports.
+        
+        Thanks to evilrabbit@tutamail.com for the security report.
+        
+        Reviewed-by: Alejandro Colomar <alx@kernel.org>
+
+M	imap/auth_gss.c
+
+2026-04-18 21:54:34 +0800  Kevin J. McCarthy  <kevin@8t8.us> (fdc04a17)
+
+        * Fix infinite loop in gpgme data_object_to_stream().
+        
+        The code was not properly checking for a -1 return value in the read,
+        leading to an infinite loop, and printing past the buffer value to the
+        stream.
+        
+        Thanks to evilrabbit@tutamail.com for the security report.
+        
+        Reviewed-by: Alejandro Colomar <alx@kernel.org>
+
+M	crypt-gpgme.c
+
+2026-04-18 21:41:23 +0800  Kevin J. McCarthy  <kevin@8t8.us> (ebfa2969)
+
+        * Fix NULL dereference in show_sig_summary().
+        
+        Inside show_one_sig_status(), if the error code is GPG_ERR_NO_PUBKEY,
+        key is NULL.  However, show_sig_summary() doesn't check for a NULL key
+        before dereferencing for the "key expired" case.
+        
+        Thanks to evilrabbit@tutamail.com for the security report.
+        
+        Thanks to Alejandro Colomar for his review and suggestion to keep the
+        ternary operator.
+        
+        Reviewed-by: Alejandro Colomar <alx@kernel.org>
+
+M	crypt-gpgme.c
+
+2026-03-20 13:26:34 +0800  Kevin J. McCarthy  <kevin@8t8.us> (519d1b9b)
+
+        * automatic post-release commit for mutt-2.3.1
+
+M	ChangeLog
+M	VERSION
+
 2026-03-20 13:18:13 +0800  Kevin J. McCarthy  <kevin@8t8.us> (0f1f138c)
 
         * Update UPDATING file for 2.3.1 release.
diff --git a/UPDATING b/UPDATING
index b5d55ddd..e2b17be2 100644
--- a/UPDATING
+++ b/UPDATING
@@ -9,6 +9,10 @@ http://www.mutt.org/relnotes/
 The keys used are:
   !: modified feature, -: deleted feature, +: new feature
 
+2.3.2 (2026-04-26):
+
+  ! Bug fix release.
+
 2.3.1 (2026-03-20):
 
   ! Bug fix release.
diff --git a/VERSION b/VERSION
index 2bf1c1cc..f90b1afc 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.3.1
+2.3.2
diff --git a/crypt-gpgme.c b/crypt-gpgme.c
index 5313d6f2..3195ed79 100644
--- a/crypt-gpgme.c
+++ b/crypt-gpgme.c
@@ -742,7 +742,7 @@ static int data_object_to_stream (gpgme_data_t data, FILE *fp)
       return -1;
     }
 
-  while ((nread = gpgme_data_read (data, buf, sizeof (buf))))
+  while ((nread = gpgme_data_read(data, buf, sizeof (buf))) > 0)
     {
       /* fixme: we are not really converting CRLF to LF but just
          skipping CR. Doing it correctly needs a more complex logic */
@@ -1425,7 +1425,7 @@ static int show_sig_summary (unsigned long sum,
 
   if ((sum & GPGME_SIGSUM_KEY_EXPIRED))
     {
-      time_t at = key->subkeys->expires ? key->subkeys->expires : 0;
+      time_t at = (key && key->subkeys) ? key->subkeys->expires : 0;
       if (at)
         {
           state_puts (_("Warning: The key used to create the "
diff --git a/imap/auth_cram.c b/imap/auth_cram.c
index 6080ea47..6a265de6 100644
--- a/imap/auth_cram.c
+++ b/imap/auth_cram.c
@@ -149,7 +149,7 @@ static void hmac_md5 (const char* password, char* challenge,
   if (secret_len > MD5_BLOCK_LEN)
   {
     md5_buffer (password, secret_len, hash_passwd);
-    strfcpy ((char*) secret, (char*) hash_passwd, MD5_DIGEST_LEN);
+    memcpy(secret, hash_passwd, MD5_DIGEST_LEN);
     secret_len = MD5_DIGEST_LEN;
   }
   else
diff --git a/imap/auth_gss.c b/imap/auth_gss.c
index 4fb6d7e1..fb9e50d8 100644
--- a/imap/auth_gss.c
+++ b/imap/auth_gss.c
@@ -108,7 +108,7 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
   int cflags;
   OM_uint32 maj_stat, min_stat;
   BUFFER *buf1 = NULL, *buf2 = NULL;
-  unsigned long buf_size;
+  uint32_t buf_size;
   int rc, retval = IMAP_AUTH_FAILURE;
 
   if (!mutt_bit_isset (idata->capabilities, AGSSAPI))
@@ -259,6 +259,14 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
   }
   dprint (2, (debugfile, "Credential exchange complete\n"));
 
+  if (send_token.length < 4)
+  {
+    /* TODO: convert to muttdbg() in master branch merge */
+    dprint(2, (debugfile, "Truncated security level data\n"));
+    gss_release_buffer(&min_stat, &send_token);
+    goto err_abort_cmd;
+  }
+
   /* first octet is security levels supported. We want NONE */
 #ifdef DEBUG
   server_conf_flags = ((char*) send_token.value)[0];
@@ -272,7 +280,7 @@ imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method)
 
   /* we don't care about buffer size if we don't wrap content. But here it is */
   ((char*) send_token.value)[0] = 0;
-  buf_size = ntohl (*((long *) send_token.value));
+  buf_size = ntohl(*((uint32_t *) send_token.value));
   gss_release_buffer (&min_stat, &send_token);
   dprint (2, (debugfile, "Unwrapped security level flags: %c%c%c\n",
               server_conf_flags & GSS_AUTH_P_NONE      ? 'N' : '-',
diff --git a/url.c b/url.c
index 19a52443..fddbc6f2 100644
--- a/url.c
+++ b/url.c
@@ -60,7 +60,9 @@ static int url_pct_decode (char *s)
       if (s[1] && s[2] &&
 	  isxdigit ((unsigned char) s[1]) &&
 	  isxdigit ((unsigned char) s[2]) &&
-	  hexval (s[1]) >= 0 && hexval (s[2]) >= 0)
+          hexval(s[1]) >= 0 && hexval(s[2]) >= 0 &&
+          // check for embedded nul
+          (hexval(s[1]) > 0 || hexval(s[2]) > 0))
       {
 	*d++ = (hexval (s[1]) << 4) | (hexval (s[2]));
 	s += 2;
