diff --git a/cbits/linenoise.c b/cbits/linenoise.c
--- a/cbits/linenoise.c
+++ b/cbits/linenoise.c
@@ -125,6 +125,7 @@
 static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
 
 static struct termios orig_termios; /* In order to restore at exit.*/
+static int maskmode = 0; /* Show "***" instead of input. For passwords. */
 static int rawmode = 0; /* For atexit() function to check if restore is needed*/
 static int mlmode = 0;  /* Multi line mode. Default is single line. */
 static int atexit_registered = 0; /* Register atexit just 1 time. */
@@ -197,6 +198,19 @@
 
 /* ======================= Low level terminal handling ====================== */
 
+/* Enable "mask mode". When it is enabled, instead of the input that
+ * the user is typing, the terminal will just display a corresponding
+ * number of asterisks, like "****". This is useful for passwords and other
+ * secrets that should not be displayed. */
+void linenoiseMaskModeEnable(void) {
+    maskmode = 1;
+}
+
+/* Disable mask mode. */
+void linenoiseMaskModeDisable(void) {
+    maskmode = 0;
+}
+
 /* Set if to use or not the multi line mode. */
 void linenoiseSetMultiLine(int ml) {
     mlmode = ml;
@@ -525,7 +539,11 @@
     abAppend(&ab,seq,strlen(seq));
     /* Write the prompt and the current buffer content */
     abAppend(&ab,l->prompt,strlen(l->prompt));
-    abAppend(&ab,buf,len);
+    if (maskmode == 1) {
+        while (len--) abAppend(&ab,"*",1);
+    } else {
+        abAppend(&ab,buf,len);
+    }
     /* Show hits if any. */
     refreshShowHints(&ab,l,plen);
     /* Erase to right */
@@ -579,7 +597,12 @@
 
     /* Write the prompt and the current buffer content */
     abAppend(&ab,l->prompt,strlen(l->prompt));
-    abAppend(&ab,l->buf,l->len);
+    if (maskmode == 1) {
+        unsigned int i;
+        for (i = 0; i < l->len; i++) abAppend(&ab,"*",1);
+    } else {
+        abAppend(&ab,l->buf,l->len);
+    }
 
     /* Show hits if any. */
     refreshShowHints(&ab,l,plen);
@@ -647,7 +670,8 @@
             if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
                 /* Avoid a full update of the line in the
                  * trivial case. */
-                if (write(l->ofd,&c,1) == -1) return -1;
+                char d = (maskmode==1) ? '*' : c;
+                if (write(l->ofd,&d,1) == -1) return -1;
             } else {
                 refreshLine(l);
             }
diff --git a/cbits/linenoise.h b/cbits/linenoise.h
--- a/cbits/linenoise.h
+++ b/cbits/linenoise.h
@@ -65,6 +65,8 @@
 void linenoiseClearScreen(void);
 void linenoiseSetMultiLine(int ml);
 void linenoisePrintKeyCodes(void);
+void linenoiseMaskModeEnable(void);
+void linenoiseMaskModeDisable(void);
 
 #ifdef __cplusplus
 }
diff --git a/linenoise.cabal b/linenoise.cabal
--- a/linenoise.cabal
+++ b/linenoise.cabal
@@ -1,13 +1,13 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.31.2.
+-- This file has been generated from package.yaml by hpack version 0.33.0.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 0ac5eb636f96c454e97cd9b0a65b0d2913dc32021ff8cd9f88d16ca0f4fd561a
+-- hash: 5a1085adefd24661dcb4f86110e45c0a9535af0b1e7189721c089f0eae60c5d2
 
 name:           linenoise
-version:        0.3.1
+version:        0.3.2
 synopsis:       A lightweight readline-replacement library for Haskell
 description:    Please see the README on GitHub at <https://github.com/ejconlon/haskell-linenoise#readme>
 category:       User Interfaces
@@ -45,11 +45,11 @@
       cbits/linenoise.h
   build-depends:
       base >=4.12 && <5
-    , bytestring >=0.10
-    , exceptions >=0.10
-    , mtl >=2.2
-    , text >=1.2
-    , unliftio-core >=0.1
+    , bytestring >=0.10 && <1
+    , exceptions >=0.10 && <1
+    , mtl >=2.2 && <3
+    , text >=1.2 && <2
+    , unliftio-core >=0.1 && <1
   default-language: Haskell2010
 
 executable linenoise-demo
@@ -61,10 +61,10 @@
   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       base >=4.12 && <5
-    , bytestring >=0.10
-    , exceptions >=0.10
+    , bytestring >=0.10 && <1
+    , exceptions >=0.10 && <1
     , linenoise
-    , mtl >=2.2
-    , text >=1.2
-    , unliftio-core >=0.1
+    , mtl >=2.2 && <3
+    , text >=1.2 && <2
+    , unliftio-core >=0.1 && <1
   default-language: Haskell2010
diff --git a/src/Linenoise/FFI.hs b/src/Linenoise/FFI.hs
--- a/src/Linenoise/FFI.hs
+++ b/src/Linenoise/FFI.hs
@@ -1,5 +1,3 @@
-{-# LANGUAGE DeriveFunctor #-}
-{-# LANGUAGE DeriveFoldable #-}
 {-# LANGUAGE DeriveTraversable #-}
 {-# LANGUAGE ForeignFunctionInterface #-}
 
@@ -17,12 +15,14 @@
   , stifleHistory
   ) where
 
+import Control.Monad (unless)
 import Data.ByteString (ByteString)
+import qualified Data.ByteString as BS
 import qualified Data.ByteString.Unsafe as BSU
-import Data.Foldable (forM_)
-import Foreign
+import Data.Foldable (for_)
+import Foreign (FunPtr, Ptr, Storable (..), fromBool, maybePeek)
 import Foreign.C.Error (eAGAIN, getErrno, resetErrno)
-import Foreign.C.String
+import Foreign.C.String (CString, newCString)
 import Foreign.C.Types (CChar, CInt (..), CSize)
 
 foreign import ccall "linenoise.h linenoise"
@@ -71,7 +71,7 @@
 makeCompletion f buf lc = do
   line <- BSU.unsafePackCString buf
   comps <- f line
-  forM_ comps (`BSU.unsafeUseAsCString` linenoiseAddCompletion lc)
+  for_ comps (\c -> unless (BS.null c) (BSU.unsafeUseAsCString c (linenoiseAddCompletion lc)))
 
 -- | Result of getInputLine.
 data InputResult a
@@ -93,8 +93,8 @@
 
 -- | Add to current history.
 addHistory :: ByteString -> IO ()
-addHistory =
-  flip BSU.unsafeUseAsCString $ \str -> do
+addHistory bs =
+  unless (BS.null bs) $ BSU.unsafeUseAsCString bs $ \str -> do
     _ <- linenoiseHistoryAdd str
     pure ()
 
diff --git a/src/Linenoise/Repl.hs b/src/Linenoise/Repl.hs
--- a/src/Linenoise/Repl.hs
+++ b/src/Linenoise/Repl.hs
@@ -13,16 +13,15 @@
 import Control.Applicative (Alternative)
 import Control.Monad (MonadPlus)
 import Control.Monad.Catch (MonadCatch, MonadThrow)
-import Control.Monad.Fail (MonadFail)
 import Control.Monad.Fix (MonadFix)
 import Control.Monad.IO.Class (MonadIO (..))
-import Control.Monad.IO.Unlift (MonadUnliftIO (..), UnliftIO (..))
+import Control.Monad.IO.Unlift (MonadUnliftIO (..), wrappedWithRunInIO)
 import Control.Monad.Reader (MonadReader, ReaderT (..), ask)
 import Control.Monad.State.Strict (MonadState (..))
 import Control.Monad.Trans (MonadTrans (..))
 import Control.Monad.Zip (MonadZip)
-import Data.Text (Text)
 import Data.IORef (IORef, newIORef, readIORef, writeIORef)
+import Data.Text (Text)
 import Linenoise.Unlift (InputResult (..))
 import qualified Linenoise.Unlift as Unlift
 
@@ -45,11 +44,10 @@
   lift = ReplT . lift . lift
 
 instance MonadUnliftIO m => MonadUnliftIO (ReplT r s m) where
-  askUnliftIO = do
-    UnliftIO run <- lift askUnliftIO
+  withRunInIO run = do
     r <- ask
-    ref <- askRef
-    pure (UnliftIO (\n -> run (refReplT n r ref)))
+    ref <-askRef
+    wrappedWithRunInIO lift (\n -> refReplT n r ref) run
 
 instance MonadIO m => MonadState s (ReplT r s m) where
   get = ReplT (ReaderT (const (ReaderT (liftIO . readIORef))))
