clod-0.1.12: 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
## Why This Matters
When an AI assistant like Claude interacts with a filesystem, strict security boundaries are essential. The capability system in Clod ensures that:
1. Operations can only access files in explicitly allowed directories
2. Path traversal attacks (using `../` or symbolic links) are prevented
3. Capabilities must be explicitly passed to functions that need them
4. The security model is made visible in the code through explicit tokens
This approach significantly reduces the risk of accidentally exposing sensitive files or allowing unauthorized modifications.
## 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