7.9 KiB
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 | ✅ |
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
TaskPanickederror - 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
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:
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
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:
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:
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:
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:
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
- No resource limits: Tasks can consume unlimited CPU, memory, or time
- No isolation: All tasks run in the same process with same privileges
- 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:
- DO NOT open a public GitHub issue
- Email the maintainer directly: steve@waits.net
- 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
License
This security policy is part of the dagx project and is licensed under the MIT License.