diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,10 @@
+[0.11.5.4] — January 2025
+
+* Bug fixes:
+  * [`Builder`: avoid unsound buffer reuse, introduced in `bytestring-0.11.5.0`](https://github.com/haskell/bytestring/pull/691)
+
+[0.11.5.4]: https://github.com/haskell/bytestring/compare/0.11.5.3...0.11.5.4
+
 [0.11.5.3] — October 2023
 
 * Bug fixes:
diff --git a/Data/ByteString/Builder/Internal.hs b/Data/ByteString/Builder/Internal.hs
--- a/Data/ByteString/Builder/Internal.hs
+++ b/Data/ByteString/Builder/Internal.hs
@@ -1096,7 +1096,7 @@
                 -- Checking for empty case avoids allocating 'n-1' empty
                 -- buffers for 'n' insertChunkH right after each other.
                 if isEmpty
-                  then fill nextStep (Buffer fpbuf (BufferRange pbuf pe))
+                  then fill nextStep buf
                   else do buf' <- nextBuffer (Just (buf, bufSize))
                           fill nextStep buf'
 
@@ -1108,9 +1108,9 @@
           | trim chunkSize size = do
               bs <- S.createFp chunkSize $ \fpbuf' ->
                         S.memcpyFp fpbuf' fpbuf chunkSize
-              -- Instead of allocating a new buffer after trimming,
-              -- we re-use the old buffer and consider it empty.
-              return $ Yield1 bs (mkCIOS True)
+              -- It is not safe to re-use the old buffer (see #690),
+              -- so we allocate a new buffer after trimming.
+              return $ Yield1 bs (mkCIOS False)
           | otherwise            =
               return $ Yield1 (S.BS fpbuf chunkSize) (mkCIOS False)
           where
diff --git a/bytestring.cabal b/bytestring.cabal
--- a/bytestring.cabal
+++ b/bytestring.cabal
@@ -1,5 +1,5 @@
 Name:                bytestring
-Version:             0.11.5.3
+Version:             0.11.5.4
 Synopsis:            Fast, compact, strict and lazy byte strings with a list interface
 Description:
     An efficient compact, immutable byte string type (both strict and lazy)
