diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # Revision history for shh
 
+## 0.7.1.2 -- 2021-06-30
+
+* Remove assumptions introduced in 0.7.1.0 about UTF-8. Arbitrary filenames
+  will now work as they should.
+
 ## 0.7.1.1 -- 2021-06-30
 
 * Changes to work on GHC 9
diff --git a/shh.cabal b/shh.cabal
--- a/shh.cabal
+++ b/shh.cabal
@@ -1,5 +1,5 @@
 name:                shh
-version:             0.7.1.1
+version:             0.7.1.2
 synopsis:            Simple shell scripting from Haskell
 description:         Provides a shell scripting environment for Haskell. It
                      helps you use external binaries, and allows you to
diff --git a/src/Shh/Internal.hs b/src/Shh/Internal.hs
--- a/src/Shh/Internal.hs
+++ b/src/Shh/Internal.hs
@@ -42,6 +42,7 @@
 import GHC.IO.BufferedIO
 import GHC.IO.Device as IODevice hiding (read)
 import GHC.IO.Encoding
+import GHC.Foreign (peekCStringLen)
 import GHC.IO.Exception (IOErrorType(ResourceVanished))
 import GHC.IO.Handle hiding (hGetContents)
 import GHC.IO.Handle.Internals
@@ -188,17 +189,36 @@
     pure $! v
 infixl 1 |!>
 
+-- | Things that can be converted to a @`FilePath`@.
 --
+-- The results must use the file system encoding.
+class ToFilePath a where
+    toFilePath :: a -> IO FilePath
+
+instance ToFilePath FilePath where
+    toFilePath = pure
+
+instance ToFilePath ByteString.ByteString where
+    toFilePath bs = do
+        enc <- getFileSystemEncoding
+        ByteString.useAsCStringLen bs (peekCStringLen enc)
+
+instance ToFilePath ByteString where
+    toFilePath = toFilePath . toStrict
+
+--
 -- | Redirect stdout of this process to another location
 --
 -- >>> echo "Ignore me" &> Append "/dev/null"
 (&>) :: Shell f => Proc a -> Stream -> f a
 p &> StdOut = runProc p
 (Proc f) &> StdErr = buildProc $ \i _ e -> f i e e
-(Proc f) &> (Truncate path) = buildProc $ \i _ e ->
-    withBinaryFile (toString path) WriteMode $ \h -> f i h e
-(Proc f) &> (Append path) = buildProc $ \i _ e ->
-    withBinaryFile (toString path) AppendMode $ \h -> f i h e
+(Proc f) &> (Truncate path) = buildProc $ \i _ e -> do
+    path' <- toFilePath path
+    withBinaryFile path' WriteMode $ \h -> f i h e
+(Proc f) &> (Append path) = buildProc $ \i _ e -> do
+    path' <- toFilePath path
+    withBinaryFile path' AppendMode $ \h -> f i h e
 infixl 9 &>
 
 -- | Redirect stderr of this process to another location
@@ -208,10 +228,12 @@
 (&!>) :: Shell f => Proc a -> Stream -> f a
 p &!> StdErr = runProc $ p
 (Proc f) &!> StdOut = buildProc $ \i o _ -> f i o o
-(Proc f) &!> (Truncate path) = buildProc $ \i o _ ->
-    withBinaryFile (toString path) WriteMode $ \h -> f i o h
-(Proc f) &!> (Append path) = buildProc $ \i o _ ->
-    withBinaryFile (toString path) AppendMode $ \h -> f i o h
+(Proc f) &!> (Truncate path) = buildProc $ \i o _ -> do
+    path' <- toFilePath path
+    withBinaryFile path' WriteMode $ \h -> f i o h
+(Proc f) &!> (Append path) = buildProc $ \i o _ -> do
+    path' <- toFilePath path
+    withBinaryFile path' AppendMode $ \h -> f i o h
 infixl 9 &!>
 
 -- | Lift a Haskell function into a @`Proc`@. The handles are the @stdin@
@@ -434,9 +456,8 @@
 -- or not. Most uses of @`mkProc'`@ in Shh do not delegate control-c.
 mkProc' :: HasCallStack => Bool -> ByteString -> [ByteString] -> Proc ()
 mkProc' delegate cmd args = Proc $ \i o e -> do
-    let
-        cmd' = toString cmd
-        args' = toString <$> args
+    cmd' <- toFilePath cmd
+    args' <- mapM toFilePath args
     bracket
         (createProcess_ cmd' (proc cmd' args')
             { std_in = UseHandle i
