diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# v1.0.1.11
+
+## Fixes
+
+- Fixed `Pqi.connectionNeedsPassword` and `Pqi.connectionUsedPassword` tracking the conninfo `password=` field instead of libpq's `password_needed` auth-exchange state. `connectionNeedsPassword` was hardcoded `pure False`; `connectionUsedPassword` was `not (ByteString.null password)`, so it reported `True` whenever a password string was merely present in the conninfo, trust auth or not, and `connectionNeedsPassword` never reported `True` even when SCRAM authentication was challenged for and no password was supplied. `Pqi.Native.Connection.Connection` now carries a `passwordNeeded` flag, set during the handshake only when the server actually sends `AuthenticationCleartextPassword`/`MD5Password`/`SASL`, and reset on `reconnect`; both functions now derive from that state, matching `fe-auth.c`'s `password_needed` semantics exactly. Caught by the `pqi-conformance` specs `Pqi.Conformance.Operation.ConnectionNeedsPassword` and `Pqi.Conformance.Operation.ConnectionUsedPassword`.
+
 # v1.0.1.10
 
 ## Fixes
diff --git a/pqi-native.cabal b/pqi-native.cabal
--- a/pqi-native.cabal
+++ b/pqi-native.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: pqi-native
-version: 1.0.1.10
+version: 1.0.1.11
 category: Database, PostgreSQL
 synopsis: Native (pure-Haskell) adapter for pqi
 description:
@@ -158,5 +158,5 @@
   build-depends:
     base >=4.11 && <5,
     hspec >=2.11 && <2.12,
-    pqi-conformance ^>=1.0.9,
+    pqi-conformance ^>=1.0.11,
     pqi-native,
diff --git a/src/library/Pqi/Native.hs b/src/library/Pqi/Native.hs
--- a/src/library/Pqi/Native.hs
+++ b/src/library/Pqi/Native.hs
@@ -101,8 +101,10 @@
         fd <- Transport.socketFd transport
         pure (Just (fromIntegral fd :: Fd)),
       Pqi.backendPID = maybe 0 fst <$> readIORef (Connection.backendKey connection),
-      Pqi.connectionNeedsPassword = pure False,
-      Pqi.connectionUsedPassword = pure (not (ByteString.null (Connection.password (Connection.info connection)))),
+      Pqi.connectionNeedsPassword = do
+        needed <- readIORef (Connection.passwordNeeded connection)
+        pure (needed && ByteString.null (Connection.password (Connection.info connection))),
+      Pqi.connectionUsedPassword = readIORef (Connection.passwordNeeded connection),
       Pqi.exec = \sql -> fmap mkResult <$> Query.exec connection sql,
       Pqi.execParams = \sql params resultFormat ->
         fmap mkResult <$> Query.execParams connection sql params resultFormat,
diff --git a/src/library/Pqi/Native/Connection.hs b/src/library/Pqi/Native/Connection.hs
--- a/src/library/Pqi/Native/Connection.hs
+++ b/src/library/Pqi/Native/Connection.hs
@@ -314,7 +314,13 @@
     -- | The SQL text of the most recently sent query (set by sendQuery /
     -- sendQueryParams). Used when formatting error messages for async results
     -- so that @LINE N:@ position context can be reproduced.
-    currentQuery :: IORef ByteString
+    currentQuery :: IORef ByteString,
+    -- | Set during the handshake when the server actually challenges for a
+    -- password (@AuthenticationCleartextPassword@\/@MD5Password@\/@SASL@),
+    -- mirroring libpq's @password_needed@. Trust\/peer\/GSS authentication
+    -- never sets this, regardless of whether a password was supplied in the
+    -- conninfo.
+    passwordNeeded :: IORef Bool
   }
 
 -- | Send a serialized frontend message.
@@ -532,6 +538,7 @@
   writeIORef (txStatus connection) 0x49
   writeIORef (connStatus connection) ConnectionBad
   writeIORef (lastError connection) (Just "")
+  writeIORef (passwordNeeded connection) False
   sendMessage connection (startupMessage (startupParams (info connection)))
   handshake connection
 
@@ -564,6 +571,7 @@
     <*> newIORef Seq.empty
     <*> newIORef ErrorsDefault
     <*> newIORef ""
+    <*> newIORef False
 
 -- | The startup\/authentication state machine, ending at the first
 -- @ReadyForQuery@ (success) or @ErrorResponse@ (failure).
@@ -575,13 +583,16 @@
       case message of
         AuthenticationOk -> startingUp
         AuthenticationCleartextPassword -> do
+          writeIORef (passwordNeeded connection) True
           sendMessage connection (passwordMessage (password (info connection)))
           authenticating
         AuthenticationMD5Password salt -> do
+          writeIORef (passwordNeeded connection) True
           let response = Auth.md5Password (user (info connection)) (password (info connection)) salt
           sendMessage connection (passwordMessage response)
           authenticating
-        AuthenticationSASL mechanisms ->
+        AuthenticationSASL mechanisms -> do
+          writeIORef (passwordNeeded connection) True
           Auth.scram (user (info connection)) (password (info connection)) mechanisms (saslExchange connection) >>= \case
             Left problem -> setError connection problem
             Right () -> startingUp
