308 lines
7.9 KiB
Markdown
308 lines
7.9 KiB
Markdown
# Security Policy
|
|
|
|
## Overview
|
|
|
|
dagx is a task execution library designed for safety and predictability. This document outlines security considerations, best practices, and limitations you should be aware of when using dagx in security-sensitive contexts.
|
|
|
|
## Supported Versions
|
|
|
|
| Version | Supported |
|
|
| ------- | ------------------ |
|
|
| 0.3.x | :white_check_mark: |
|
|
|
|
## Security Model
|
|
|
|
### What dagx Provides
|
|
|
|
- **Compile-time type safety**: Dependencies are validated at compile time, preventing type errors
|
|
- **Cycle detection**: Runtime detection of circular dependencies
|
|
- **Memory safety**: Rust's ownership system prevents memory corruption
|
|
- **Panic isolation**: Panics in any task will be caught and not propagated through the executor
|
|
|
|
### What dagx Does NOT Provide
|
|
|
|
- **Resource limits**: No built-in timeout, memory limits, or CPU throttling
|
|
- **Sandboxing**: Tasks run with full process privileges
|
|
- **Authentication/Authorization**: No access control between tasks
|
|
- **Input validation**: Tasks must validate their own inputs
|
|
|
|
## Security Considerations
|
|
|
|
### 1. Panic Handling
|
|
|
|
**Current Behavior**: dagx isolates panics between tasks. If a task panics:
|
|
|
|
- The panic will be caught and converted to a `TaskPanicked` error
|
|
- Other tasks in the same execution layer will complete
|
|
- The entire DAG execution will terminate after layer execution has finished and return **only the first panic**
|
|
|
|
**Recommendation**:
|
|
|
|
- Return `Result<T, E>` from tasks and handle errors explicitly
|
|
- Test panic scenarios thoroughly
|
|
|
|
```rust
|
|
use dagx::{task, Task};
|
|
use std::panic;
|
|
|
|
struct SafeTask;
|
|
|
|
#[task]
|
|
impl SafeTask {
|
|
async fn run(&mut self) -> Result<i32, String> {
|
|
// Catch panics and convert to Result
|
|
panic::catch_unwind(|| {
|
|
// Potentially panicking code
|
|
42
|
|
})
|
|
.map_err(|_| "Task panicked".to_string())
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Resource Limits
|
|
|
|
dagx does NOT enforce:
|
|
|
|
- Execution timeouts
|
|
- Memory limits
|
|
- CPU usage limits
|
|
- Task queue depth limits
|
|
|
|
**Recommendation**: Implement resource limits at the task level using your async runtime's facilities:
|
|
|
|
```rust
|
|
use dagx::{task, Task};
|
|
use tokio::time::{timeout, Duration};
|
|
|
|
struct TimeLimitedTask;
|
|
|
|
#[task]
|
|
impl TimeLimitedTask {
|
|
async fn run(&mut self) -> Result<i32, String> {
|
|
timeout(Duration::from_secs(30), async {
|
|
// Your task logic here
|
|
expensive_computation().await
|
|
})
|
|
.await
|
|
.map_err(|_| "Task timed out".to_string())
|
|
}
|
|
}
|
|
|
|
async fn expensive_computation() -> i32 {
|
|
// Actual work
|
|
42
|
|
}
|
|
```
|
|
|
|
### 3. Untrusted Input
|
|
|
|
If your DAG processes untrusted input:
|
|
|
|
- **Validate all inputs** at task boundaries
|
|
- **Sanitize outputs** before using in security-sensitive contexts
|
|
- **Limit resource consumption** (see Resource Limits above)
|
|
- **Use strongly-typed inputs** to leverage compile-time safety
|
|
|
|
```rust
|
|
use dagx::{task, Task};
|
|
|
|
struct ValidatedInput {
|
|
max_size: usize,
|
|
}
|
|
|
|
#[task]
|
|
impl ValidatedInput {
|
|
async fn run(&mut self, input: &String) -> Result<String, String> {
|
|
// Validate input size
|
|
if input.len() > self.max_size {
|
|
return Err(format!("Input exceeds maximum size of {}", self.max_size));
|
|
}
|
|
|
|
// Validate input format
|
|
if !input.chars().all(|c| c.is_alphanumeric()) {
|
|
return Err("Input contains invalid characters".to_string());
|
|
}
|
|
|
|
Ok(input.clone())
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Dependency Injection
|
|
|
|
Tasks receive dependencies as references, which:
|
|
|
|
- **Prevents modification** of upstream results (read-only access)
|
|
- **Ensures consistency** across multiple dependent tasks
|
|
- **Avoids data races** through Rust's borrow checker
|
|
|
|
However, if your task output contains interior mutability (e.g., `Arc<Mutex<T>>`), be aware that:
|
|
|
|
- Multiple downstream tasks can mutate shared state
|
|
- This is intentional but requires careful synchronization
|
|
|
|
### 5. State Management
|
|
|
|
Tasks with mutable state (`&mut self`):
|
|
|
|
- Execute exactly once per DAG run
|
|
- Have exclusive access to their internal state during execution
|
|
- Are not reentrant (cannot execute concurrently with themselves)
|
|
|
|
**Safe**: Each task instance maintains isolated state
|
|
**Unsafe**: Sharing mutable state between tasks via global variables or `Arc<Mutex<T>>` requires manual synchronization
|
|
|
|
## Best Practices
|
|
|
|
### 1. Error Handling
|
|
|
|
Use `Result<T, E>` for all fallible operations:
|
|
|
|
```rust
|
|
use dagx::{task, Task};
|
|
|
|
struct FallibleTask;
|
|
|
|
#[task]
|
|
impl FallibleTask {
|
|
async fn run(&mut self, input: &String) -> Result<i32, String> {
|
|
input.parse::<i32>()
|
|
.map_err(|e| format!("Parse error: {}", e))
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Timeouts
|
|
|
|
Implement timeouts for long-running or potentially-hanging operations:
|
|
|
|
```rust
|
|
use tokio::time::{timeout, Duration};
|
|
|
|
#[task]
|
|
impl MyTask {
|
|
async fn run(&mut self) -> Result<String, String> {
|
|
timeout(Duration::from_secs(10), async {
|
|
// Network request or other potentially-slow operation
|
|
fetch_data().await
|
|
})
|
|
.await
|
|
.map_err(|_| "Operation timed out".to_string())?
|
|
}
|
|
}
|
|
|
|
async fn fetch_data() -> Result<String, String> {
|
|
Ok("data".to_string())
|
|
}
|
|
```
|
|
|
|
### 3. Resource Cleanup
|
|
|
|
Use RAII patterns and `Drop` implementations for resource cleanup:
|
|
|
|
```rust
|
|
struct FileTask {
|
|
path: String,
|
|
}
|
|
|
|
#[task]
|
|
impl FileTask {
|
|
async fn run(&mut self) -> Result<String, String> {
|
|
let content = tokio::fs::read_to_string(&self.path)
|
|
.await
|
|
.map_err(|e| format!("File read error: {}", e))?;
|
|
Ok(content)
|
|
}
|
|
}
|
|
// File handle automatically closed when task completes
|
|
```
|
|
|
|
### 4. Logging and Monitoring
|
|
|
|
Add logging to tasks for observability:
|
|
|
|
```rust
|
|
use dagx::{task, Task};
|
|
|
|
struct MonitoredTask;
|
|
|
|
#[task]
|
|
impl MonitoredTask {
|
|
async fn run(&mut self, input: &i32) -> i32 {
|
|
println!("Task started with input: {}", input);
|
|
let result = input * 2;
|
|
println!("Task completed with result: {}", result);
|
|
result
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Minimize Privilege
|
|
|
|
- Run dagx applications with minimum necessary privileges
|
|
- Avoid running as root or with elevated permissions
|
|
- Use operating system security features (containers, namespaces, etc.)
|
|
|
|
## Security Limitations
|
|
|
|
### Known Limitations
|
|
|
|
1. **No resource limits**: Tasks can consume unlimited CPU, memory, or time
|
|
2. **No isolation**: All tasks run in the same process with same privileges
|
|
3. **Shared executor**: Tasks compete for executor resources
|
|
|
|
### Not Security Features
|
|
|
|
dagx is **not** designed to:
|
|
|
|
- Run untrusted code safely
|
|
- Enforce resource quotas
|
|
- Provide sandboxing
|
|
- Implement authentication or authorization
|
|
- Prevent side-channel attacks
|
|
|
|
If you need these features, consider:
|
|
|
|
- **WebAssembly sandboxing** (wasmtime, wasmer)
|
|
- **Container isolation** (Docker, podman)
|
|
- **Operating system isolation** (VMs, seccomp, AppArmor)
|
|
- **Dedicated security runtimes** (gVisor, Firecracker)
|
|
|
|
## Reporting a Vulnerability
|
|
|
|
If you discover a security vulnerability in dagx:
|
|
|
|
1. **DO NOT** open a public GitHub issue
|
|
2. Email the maintainer directly: <steve@waits.net>
|
|
3. Include:
|
|
- Description of the vulnerability
|
|
- Steps to reproduce
|
|
- Potential impact
|
|
- Suggested fix (if available)
|
|
|
|
You should receive a response within 48 hours. If the vulnerability is confirmed:
|
|
|
|
- A fix will be developed privately
|
|
- A security advisory will be published
|
|
- Credit will be given (unless you prefer anonymity)
|
|
|
|
## Security Updates
|
|
|
|
Security updates will be:
|
|
|
|
- Released as patch versions (e.g., 0.1.1)
|
|
- Announced in GitHub releases
|
|
- Published to crates.io immediately
|
|
- Documented in CHANGELOG.md
|
|
|
|
## Additional Resources
|
|
|
|
- [Rust Security Guidelines](https://anssi-fr.github.io/rust-guide/)
|
|
- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/)
|
|
- [Tokio Security](https://tokio.rs/tokio/topics/security)
|
|
|
|
## License
|
|
|
|
This security policy is part of the dagx project and is licensed under the MIT License.
|