never executed always true always false
    1 module PureClaw.Handles.Shell
    2   ( -- * Process result
    3     ProcessResult (..)
    4     -- * Handle type
    5   , ShellHandle (..)
    6     -- * Implementations
    7   , mkShellHandle
    8   , mkNoOpShellHandle
    9   ) where
   10 
   11 import Data.ByteString (ByteString)
   12 import Data.ByteString.Lazy qualified as BL
   13 import Data.Text qualified as T
   14 import System.Exit
   15 import System.Process.Typed qualified as P
   16 
   17 import PureClaw.Handles.Log
   18 import PureClaw.Security.Command
   19 
   20 -- | Result of a subprocess execution.
   21 data ProcessResult = ProcessResult
   22   { _pr_exitCode :: ExitCode
   23   , _pr_stdout   :: ByteString
   24   , _pr_stderr   :: ByteString
   25   }
   26   deriving stock (Show, Eq)
   27 
   28 -- | Subprocess execution capability. Only accepts 'AuthorizedCommand',
   29 -- which is proof that the command passed security policy evaluation.
   30 --
   31 -- The real implementation strips the subprocess environment to prevent
   32 -- secret leakage via inherited environment variables.
   33 newtype ShellHandle = ShellHandle
   34   { _sh_execute :: AuthorizedCommand -> IO ProcessResult
   35   }
   36 
   37 -- | Minimal safe environment for subprocesses. Provides only PATH so
   38 -- commands can be resolved, but inherits nothing else from the parent.
   39 safeEnv :: [(String, String)]
   40 safeEnv = [("PATH", "/usr/bin:/bin:/usr/local/bin")]
   41 
   42 -- | Real shell handle using @typed-process@. Strips the subprocess
   43 -- environment (provides only a minimal PATH) as noted in the architecture.
   44 mkShellHandle :: LogHandle -> ShellHandle
   45 mkShellHandle logger = ShellHandle
   46   { _sh_execute = \cmd -> do
   47       let prog = getCommandProgram cmd
   48           args = map T.unpack (getCommandArgs cmd)
   49           config = P.setEnv safeEnv
   50                  $ P.proc prog args
   51       _lh_logInfo logger $ "Executing: " <> T.pack prog <> " " <> T.unwords (getCommandArgs cmd)
   52       (exitCode, outLazy, errLazy) <- P.readProcess config
   53       pure ProcessResult
   54         { _pr_exitCode = exitCode
   55         , _pr_stdout   = BL.toStrict outLazy
   56         , _pr_stderr   = BL.toStrict errLazy
   57         }
   58   }
   59 
   60 -- | No-op shell handle. Returns success with empty output.
   61 mkNoOpShellHandle :: ShellHandle
   62 mkNoOpShellHandle = ShellHandle
   63   { _sh_execute = \_ -> pure ProcessResult
   64       { _pr_exitCode = ExitSuccess
   65       , _pr_stdout   = ""
   66       , _pr_stderr   = ""
   67       }
   68   }