diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+keysafe (0.20170811) unstable; urgency=medium
+
+  * Updated to http-client 0.5.3, servant 0.11, and stackage lts-9.0.
+
+ -- Joey Hess <id@joeyh.name>  Fri, 11 Aug 2017 18:57:15 -0400
+
 keysafe (0.20170303) unstable; urgency=medium
 
   * Updated to use raaz-0.1.1.
diff --git a/CmdLine.hs b/CmdLine.hs
--- a/CmdLine.hs
+++ b/CmdLine.hs
@@ -14,6 +14,7 @@
 import Tunables
 import qualified Gpg
 import Options.Applicative
+import Data.Monoid
 import qualified Data.ByteString.UTF8 as BU8
 import qualified Data.Text as T
 import System.Directory
diff --git a/Encryption.hs b/Encryption.hs
--- a/Encryption.hs
+++ b/Encryption.hs
@@ -20,7 +20,6 @@
 import qualified Data.Text.Encoding as E
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Char8 as B8
-import qualified Data.ByteString.UTF8 as BU8
 import Text.Read
 
 type AesKey = Raaz.KEY256
@@ -202,7 +201,10 @@
 
 -- | Encoded, so that it does not contain any NULs.
 sha :: B.ByteString -> B.ByteString
-sha = BU8.fromString . Raaz.showBase16 . Raaz.sha256
+sha = Raaz.toByteString . enc . Raaz.sha256
+  where
+	enc :: Raaz.Encodable a => a -> Raaz.Base16
+	enc = Raaz.encode
 
 padBytes :: Int -> B.ByteString -> B.ByteString
 padBytes n b = b <> padding
diff --git a/HTTP/Client.hs b/HTTP/Client.hs
--- a/HTTP/Client.hs
+++ b/HTTP/Client.hs
@@ -15,11 +15,8 @@
 import Servant.Client
 import Data.Proxy
 import Network.HTTP.Client hiding (port, host, Proxy)
-import Network.HTTP.Client.Internal (Connection, makeConnection)
-import Control.Monad.Trans.Except (ExceptT, runExceptT)
+import Network.HTTP.Client.Internal (Connection)
 import Control.Exception
-import qualified Network.Socket
-import Network.Socket.ByteString (sendAll, recv)
 import Network.Socks5
 import qualified Data.ByteString.UTF8 as BU8
 import Data.List
@@ -28,10 +25,10 @@
 httpAPI :: Proxy HttpAPI
 httpAPI = Proxy
 
-motd :: Manager -> BaseUrl -> ClientM Motd
-getObject :: StorableObjectIdent -> Maybe ProofOfWork -> Manager -> BaseUrl -> ClientM (POWGuarded StorableObject)
-putObject :: StorableObjectIdent -> Maybe ProofOfWork -> StorableObject -> Manager -> BaseUrl -> ClientM (POWGuarded StoreResult)
-countObjects :: Maybe ProofOfWork -> Manager -> BaseUrl -> ClientM (POWGuarded CountResult)
+motd :: ClientM Motd
+getObject :: StorableObjectIdent -> Maybe ProofOfWork -> ClientM (POWGuarded StorableObject)
+putObject :: StorableObjectIdent -> Maybe ProofOfWork -> StorableObject -> ClientM (POWGuarded StoreResult)
+countObjects :: Maybe ProofOfWork -> ClientM (POWGuarded CountResult)
 motd :<|> getObject :<|> putObject :<|> countObjects = client httpAPI
 
 tryA :: IO a -> IO (Either SomeException a)
@@ -43,7 +40,7 @@
 	-> (String -> a)
 	-> (r -> a)
 	-> p
-	-> (Maybe ProofOfWork -> Manager -> BaseUrl -> ExceptT ServantError IO (POWGuarded r))
+	-> (Maybe ProofOfWork -> ClientM (POWGuarded r))
 	-> IO a
 serverRequest srv onerr onsuccess p a = do
 	r <- tryA $ go Nothing maxProofOfWork
@@ -69,7 +66,7 @@
 -- comparing IP addresses (which are masked somewhat by using tor).
 serverRequest'
 	:: Server
-	-> (Manager -> BaseUrl -> ExceptT ServantError IO r)
+	-> (ClientM r)
 	-> IO (Either String r)
 serverRequest' srv a = go Nothing (serverUrls srv)
   where
@@ -77,7 +74,7 @@
 		maybe "no known address" (\err -> "server failure: " ++ show err) lasterr
 	go _ (url:urls) = do
 		manager <- torableManager
-		res <- runExceptT $ a manager url
+		res <- runClientM a (ClientEnv manager url)
 		case res of
 			Left err -> go (Just err) urls
 			Right r -> return (Right r)
@@ -103,12 +100,6 @@
 	torsockconf = defaultSocksConf "127.0.0.1" torsocksport
 	socksdomain = SocksAddrDomainName (BU8.fromString onionaddress)
 	socksaddr = SocksAddress socksdomain (fromIntegral p)
-
-socketConnection :: Network.Socket.Socket -> Int -> IO Connection
-socketConnection socket chunksize = makeConnection
-	(recv socket chunksize)
-	(sendAll socket)
-	(Network.Socket.close socket)
 
 serverUrls :: Server -> [BaseUrl]
 serverUrls srv = map go (serverAddress srv)
diff --git a/HTTP/ProofOfWork.hs b/HTTP/ProofOfWork.hs
--- a/HTTP/ProofOfWork.hs
+++ b/HTTP/ProofOfWork.hs
@@ -16,7 +16,7 @@
 import GHC.Generics
 import qualified Data.Text as T
 import qualified Data.ByteString as B
-import Data.Text.Encoding (encodeUtf8)
+import Data.Text.Encoding (decodeUtf8, encodeUtf8)
 import Raaz.Core.Encode
 import qualified Raaz
 import Data.BloomFilter.Hash
@@ -109,7 +109,7 @@
 mkRequeestID' :: RequestIDSecret -> RandomSalt -> RequestID
 mkRequeestID' (RequestIDSecret key) salt =
 	let hmac = Raaz.hmacSha256 key (encodeUtf8 $ fromRandomSalt salt)
-	in RequestID salt (T.pack (showBase16 hmac))
+	in RequestID salt $ decodeUtf8 $ Raaz.toByteString (Raaz.encode hmac :: Base16)
 
 validRequestID :: RequestIDSecret -> RequestID -> Bool
 validRequestID secret rid =
diff --git a/HTTP/Server.hs b/HTTP/Server.hs
--- a/HTTP/Server.hs
+++ b/HTTP/Server.hs
@@ -18,6 +18,7 @@
 import Storage.Local
 import Serialization ()
 import Servant
+import Network.Wai (Application)
 import Network.Wai.Handler.Warp
 import Control.Monad.IO.Class
 import Control.Concurrent
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -34,7 +34,7 @@
 		$(BUILDER) build $(BUILDEROPTIONS); \
 	fi
 	if [ "$(BUILDER)" = stack ]; then \
-		ln -sf $$(find .stack-work/ -name keysafe -type f | grep build/keysafe/keysafe | tail -n 1) keysafe; \
+		ln -sf $$(stack path --dist-dir)/build/keysafe/keysafe keysafe; \
 	else \
 		ln -sf dist/build/keysafe/keysafe keysafe; \
 	fi
diff --git a/Storage.hs b/Storage.hs
--- a/Storage.hs
+++ b/Storage.hs
@@ -73,9 +73,6 @@
 --
 -- If a server is not currently accessible, it will be queued locally.
 -- If any uploads are queued, returns True.
---
--- TODO: Add shuffling and queueing/chaffing to prevent
--- correlation of related shares.
 storeShares :: StorageLocations -> ShareIdents -> [S.Set Share] -> UpdateProgress -> IO (StoreResult, Bool, [Storage])
 storeShares (StorageLocations locs) allsis shares updateprogress = do
 	((r, anyqueued), usedlocs) <- go allsis shares [] False
diff --git a/TODO b/TODO
deleted file mode 100644
--- a/TODO
+++ /dev/null
@@ -1,100 +0,0 @@
-Soon:
-
-* Finish vetting 2 servers to Recommended.
-* Set up --check-servers in a cron job, so I know when servers are down.
-* Remove gpg key passohrase from gpg keys that keysafe backs up.
-  The reason for this is that the user may well forget their gpg key
-  passphrase, and it's *weird* to restore a key with keysafe's password
-  and then have it passphrase protected.
-  The gpg key passphrase is intended only to keep a key from being used
-  for a short period of time (a week or so) when the device holding it 
-  is known to have been compromised, so the key can be revoked.
-  This doesn't really apply to keys backed up with keysafe -- if they get
-  compromised somehow, the user won't know, and cracking the gpg passphrase
-  should be almost trivial to an attacker who was able to break keysafe's
-  password.
-  paperkey can remove gpg key passphrases. Is there any better way?
-  It might make sense for keysafe to prompt for a new gpg passphrase
-  when restoring.
-
-Later:
-
-* The attack cost display can lead to a false sense of security if the user
-  takes it as gospel. It needs to be clear that it's an estimate. This and
-  other parts of the keysafe UI need usability testing.
-* Make --gui password entry fields longer, so user does not feel they need
-  to make password short. (zenity may not allow configuring this)
-* improve restore progress bar points (update after every hash try)
-* If we retrieved enough shares successfully, but decrypt failed, must
-  be a wrong password, so prompt for re-entry and retry with those shares.
-* --no-jargon which makes the UI avoid terms like "secret key" and "crack
-  password". Do usability testing!
-* --key-value=$N which eliminates the question about password value,
-  and rejects passwords that would cost less than $N to crack at current
-  rates. This should add a combo box to the password entry form in the
-  GUI to let the user adjust the $N there.
-* In backup, only upload to N-1 servers immediately, and delay the rest
-  for up to several days, with some uploads of chaff, to prevent
-  collaborating evil servers from correlating related shards.
-* Add some random padding to http requests and responses, to make it
-  harder for traffic analysis to tell that given TOR traffic is
-  keysafe traffic.
-
-Wishlist:
-
-* Custom GUI, instead of zenity. Allows:
-  - Fewer screens by consolidating multiple prompts.
-  - Check same password entered second time and don't allow continuing
-    if not.
-  - Password strengh display, and don't allow continuing if password is too
-    weak.
-* Keep secret keys in locked memory until they're encrypted.
-  (Raaz makes this possible to do.)
-  Would be nice, but not super-important, since gpg secret keys
-  are passphrase protected anyway..
-* Don't require --totalshares and --neededshares on restore when unusual
-  values were used for backup. 
-
-  The difficulty is that the number of needed shares cannot be determined by
-  looking at shares, and guessing it wrong will result in combining
-  too few shares yielding garbage, which it will take up to an hour to
-  try to decrypt, before it can tell that more shares are needed.
-
-  This could be dealt with by including the number of needed shares in the
-  serialization of Share, but then an attacker could use it to partition
-  shares from servers. If only one person uses --neededshares=5,
-  the attacker can guess that all their shares go together.
-
-  What about including the number of needed shares in the name? Since that's
-  hashed, it's not visible to an attacker. Keysafe would need to try names
-  with 2 shares, then 3, etc, and once it found shares, it would know the
-  number needed. It should also be possible to avoid breaking backwards
-  compatability, by only including the number of shares in the name when
-  it's not the standard number. To avoid needing to re-run argon2 for each
-  try, the argon2 hash of the name could be calculated first, and then the
-  number of needed shares appended before the final sha256 hash is
-  generated.
-  
-  If an attacker is able to guess the name, and a nonstandard number of
-  shares was used, the attacker could upload other objects where they would
-  be found before the real objects. This could be used to prevent
-  restore from working. (It also makes a malicious data attack (as described
-  in https://keysafe.branchable.com/details/) possible by attackers who do not
-  control the servers.
-
-Encryption tunables changes:
-
-* Argon2d is more resistent to GPU/ASIC attack optimisation.
-  Switching from Argon2i would require new tunables, and delay restores
-  (of keys backed up using the old tunables, and when the user provides the
-  wrong name) by ~10 minutes, so deferred for now
-  until there's some other reason to change the tunables.
-* The ShareIdents derivation currently appends a number and sha256 hashes
-  to generate a stream of values. Ben M points out that HMAC is a more
-  typical way to do such a thing. Even better, a HKDF-Expand
-  (RFC5869) can generate a stream which can then be chunked up into values.  
-  Either of these would avoid a full pre-image attack on SHA-2 breaking
-  keysafe. Of course, such an SHA-2 attack would be a general security
-  disaster. HKDF may prove more robust in the face of partial SHA-2 breaks.
-  Deferred for now until tthere's some other reason to change keysafe's
-  tunables.
diff --git a/keysafe.cabal b/keysafe.cabal
--- a/keysafe.cabal
+++ b/keysafe.cabal
@@ -1,5 +1,5 @@
 Name: keysafe
-Version: 0.20170303
+Version: 0.20170811
 Cabal-Version: >= 1.8
 Maintainer: Joey Hess <joey@kitenet.net>
 Author: Joey Hess
@@ -19,7 +19,6 @@
 License-File: AGPL
 Extra-Source-Files:
   CHANGELOG
-  TODO
   INSTALL
   keysafe.1
   keysafe.service
@@ -52,18 +51,18 @@
     , unix == 2.7.*
     , filepath == 1.4.*
     , split == 0.2.*
-    , directory == 1.2.*
+    , directory (>= 1.2 && < 1.4)
     , process (>= 1.2 && < 1.5)
-    , optparse-applicative == 0.12.*
+    , optparse-applicative (>= 0.12 && < 0.14)
     , readline == 1.0.*
     , zxcvbn-c == 1.0.*
-    , servant (>= 0.7 && < 0.9)
-    , servant-server (>= 0.7 && < 0.9)
-    , servant-client (>= 0.7 && < 0.9)
-    , aeson == 0.11.*
+    , servant (>= 0.7 && < 0.12)
+    , servant-server (>= 0.7 && < 0.12)
+    , servant-client (>= 0.7 && < 0.12)
+    , aeson (>= 0.11 && < 1.2)
     , wai == 3.2.*
     , warp == 3.2.*
-    , http-client == 0.4.*
+    , http-client (>= 0.5.3 && < 0.6)
     , transformers (>= 0.4 && < 0.6)
     , stm == 2.4.*
     , socks == 0.5.*
@@ -79,7 +78,7 @@
     , unix-compat == 0.4.*
     , exceptions == 0.8.*
     , random-shuffle == 0.0.*
-    , MonadRandom == 0.4.*
+    , MonadRandom (>= 0.4 && < 0.6)
   Other-Modules:
     AutoStart
     BackupLog
