never executed always true always false
1 module PureClaw.Security.Command
2 ( -- * Authorized command type (constructor intentionally NOT exported)
3 AuthorizedCommand
4 -- * Command errors
5 , CommandError (..)
6 -- * Authorization (pure — no IO)
7 , authorize
8 -- * Read-only accessors
9 , getCommandProgram
10 , getCommandArgs
11 ) where
12
13 import Data.Text (Text)
14 import Data.Text qualified as T
15 import System.FilePath
16
17 import PureClaw.Core.Types
18 import PureClaw.Security.Policy
19
20 -- | A command that has been authorized by the security policy.
21 -- Constructor is intentionally NOT exported — the only way to obtain an
22 -- 'AuthorizedCommand' is through 'authorize'.
23 --
24 -- Note for downstream: 'ShellHandle.execute' is responsible for stripping
25 -- the subprocess environment (@setEnv (Just [])@) — environment isolation
26 -- is an execution-time concern, not a policy concern.
27 newtype AuthorizedCommand = AuthorizedCommand { unAuthorizedCommand :: (FilePath, [Text]) }
28
29 -- | Errors from command authorization.
30 data CommandError
31 = CommandNotAllowed Text -- ^ The command is not in the policy's allowed set
32 | CommandInAutonomyDeny -- ^ The policy's autonomy level is 'Deny'
33 deriving stock (Show, Eq)
34
35 -- | Authorize a command against a security policy. Pure — no IO.
36 --
37 -- Checks:
38 -- 1. Autonomy level is not 'Deny'
39 -- 2. Command basename is in the policy's allowed command set
40 authorize :: SecurityPolicy -> FilePath -> [Text] -> Either CommandError AuthorizedCommand
41 authorize policy cmd args
42 | _sp_autonomy policy == Deny =
43 Left CommandInAutonomyDeny
44 | not (isCommandAllowed policy (CommandName (T.pack (takeFileName cmd)))) =
45 Left (CommandNotAllowed (T.pack (takeFileName cmd)))
46 | otherwise =
47 Right (AuthorizedCommand (cmd, args))
48
49 -- | Get the program path from an authorized command.
50 getCommandProgram :: AuthorizedCommand -> FilePath
51 getCommandProgram = fst . unAuthorizedCommand
52
53 -- | Get the arguments from an authorized command.
54 getCommandArgs :: AuthorizedCommand -> [Text]
55 getCommandArgs = snd . unAuthorizedCommand