clod-0.1.0: CAPABILITY_SECURITY.md
# Capability-Based Security
This document details the capability-based security patterns used in Clod for secure file and resource access management.
## Core Concept
Capability-based security restricts operations based on explicit capability tokens rather than implicit permissions. A capability is an unforgeable token that grants specific permissions to perform operations.
The key principles are:
1. **Default Denial**: By default, no file access is permitted
2. **Explicit Capabilities**: Each operation requires an explicit capability token
3. **Restricted Paths**: Capabilities only allow operations within specific directory trees
4. **Capability Verification**: All operations verify capabilities before execution
## Implementation Approaches
Clod implements two capability-based security approaches:
### 1. Standard Runtime Capability System
The standard system is implemented in `Clod.Types` and used throughout the application:
```haskell
-- Grant permission to read from specific directories
data FileReadCap = FileReadCap
{ allowedReadDirs :: [FilePath] -- Directories where reading is permitted
} deriving (Show, Eq)
-- Grant permission to write to specific directories
data FileWriteCap = FileWriteCap
{ allowedWriteDirs :: [FilePath] -- Directories where writing is permitted
} deriving (Show, Eq)
-- Create capabilities
fileReadCap :: [FilePath] -> FileReadCap
fileReadCap dirs = FileReadCap { allowedReadDirs = dirs }
fileWriteCap :: [FilePath] -> FileWriteCap
fileWriteCap dirs = FileWriteCap { allowedWriteDirs = dirs }
```
### 2. Advanced Type-Level Capability System
The advanced system in `Clod.AdvancedCapability` uses type-level programming to enforce permissions at compile-time:
```haskell
-- Permission types for capabilities
data Permission = Read | Write | Execute | All
-- A path with type-level permission information
data TypedPath (p :: Permission) where
TypedPath :: FilePath -> TypedPath p
-- Capability token that grants permissions
data Capability (p :: Permission) = Capability
{ allowedDirs :: [FilePath] -- Directories this capability grants access to
}
-- Create a capability token for the given permission and directories
createCapability :: forall p. [FilePath] -> Capability p
createCapability dirs = Capability { allowedDirs = dirs }
```
## Path Validation
Both implementations use similar path validation logic to prevent path traversal attacks:
```haskell
-- Check if a path is within allowed directories
isPathAllowed :: [FilePath] -> FilePath -> IO Bool
isPathAllowed allowedDirs path = do
-- Get canonical paths to resolve any `.`, `..`, or symlinks
canonicalPath <- canonicalizePath path
-- Check if the canonical path is within any of the allowed directories
checks <- mapM (\dir -> do
canonicalDir <- canonicalizePath dir
-- A path is allowed if:
-- 1. It equals an allowed directory exactly, or
-- 2. It's a proper subdirectory (dir is a prefix and has a path separator)
let isAllowed = canonicalDir == canonicalPath ||
(canonicalDir `isPrefixOf` canonicalPath &&
length canonicalPath > length canonicalDir &&
isPathSeparator (canonicalPath !! length canonicalDir))
return isAllowed) allowedDirs
-- Return result
return (or checks)
where
isPathSeparator c = c == '/' || c == '\\'
```
## Secure Operations
All file system operations require appropriate capabilities:
```haskell
-- Safe file reading that checks capabilities
safeReadFile :: FileReadCap -> FilePath -> ClodM BS.ByteString
safeReadFile cap path = do
allowed <- liftIO $ isPathAllowed (allowedReadDirs cap) path
if allowed
then liftIO $ BS.readFile path
else do
canonicalPath <- liftIO $ canonicalizePath path
throwError $ CapabilityError $
"Access denied: Cannot read file outside allowed directories: " ++ canonicalPath
-- Safe file writing that checks capabilities
safeWriteFile :: FileWriteCap -> FilePath -> BS.ByteString -> ClodM ()
safeWriteFile cap path content = do
allowed <- liftIO $ isPathAllowed (allowedWriteDirs cap) path
if allowed
then liftIO $ BS.writeFile path content
else do
canonicalPath <- liftIO $ canonicalizePath path
throwError $ CapabilityError $
"Access denied: Cannot write file outside allowed directories: " ++ canonicalPath
-- Safe file copying that checks capabilities for both read and write
safeCopyFile :: FileReadCap -> FileWriteCap -> FilePath -> FilePath -> ClodM ()
safeCopyFile readCap writeCap src dest = do
srcAllowed <- liftIO $ isPathAllowed (allowedReadDirs readCap) src
destAllowed <- liftIO $ isPathAllowed (allowedWriteDirs writeCap) dest
if srcAllowed && destAllowed
then liftIO $ copyFile src dest
else throwError $ CapabilityError $
"Access denied: Path restrictions violated"
```
## Type-Level Operations (Advanced System)
The advanced system provides type-safe file operations:
```haskell
-- Read a file with the given capability
readFile :: forall p m. (MonadIO m, PermissionFor 'Read p)
=> Capability p -> TypedPath p -> m BS.ByteString
readFile _ (TypedPath path) = liftIO $ BS.readFile path
-- Write to a file with the given capability
writeFile :: forall p m. (MonadIO m, PermissionFor 'Write p)
=> Capability p -> TypedPath p -> BS.ByteString -> m ()
writeFile _ (TypedPath path) content = liftIO $ BS.writeFile path content
-- Check if a path is allowed by this capability and create a typed path if it is
withPath :: forall p m a. (MonadIO m)
=> Capability p -> FilePath -> (Maybe (TypedPath p) -> m a) -> m a
withPath cap path f = do
allowed <- liftIO $ isPathAllowed (allowedDirs cap) path
f $ if allowed then Just (TypedPath path) else Nothing
```
## Using Capabilities in the Application
Capabilities are typically created at the application entry point and passed down to functions that need them:
```haskell
runApp :: Config -> IO ()
runApp config = do
-- Create capabilities with appropriate permissions
let readCap = fileReadCap [config.sourceDir]
let writeCap = fileWriteCap [config.outputDir]
-- Use capabilities in operations
result <- runClodM config $ do
processFiles readCap writeCap config.files
case result of
Left err -> putStrLn $ "Error: " ++ show err
Right _ -> putStrLn "Processing complete"
```
## Security Benefits
This capability-based approach provides several important security benefits:
1. **Path Traversal Prevention**: Files outside allowed directories cannot be accessed, even with path traversal attacks
2. **Explicit Permission Model**: The code clearly indicates which operations are permitted and where
3. **Principle of Least Privilege**: Components only get access to the specific directories they need
4. **Transparent Intentions**: Code that needs file access must explicitly request capabilities
5. **Compile-Time Checks**: The advanced system catches permission errors at compile time with type-level constraints
6. **Composable Security**: Capabilities can be restricted and combined
7. **Testable**: Security restrictions can be verified through automated tests
## Future Directions
For future versions of Clod, we're considering:
1. **More Granular Capabilities**: Adding more specialized capabilities (e.g., for specific operations)
2. **Enhanced Type-Level Guarantees**: Extending the type-level verification of capabilities
3. **Better Error Messages**: Improving error messages for capability violations
4. **Capability Composition**: Making it easier to compose and transform capabilities
5. **Effect Integration**: Deeper integration with algebraic effects systems