feat: dagrunner, initial version
This commit is contained in:
122 files changed
+23101
No files matched your search
+311
@@ -0,0 +1,311 @@
|
||||
# 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.1.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
|
||||
- **Thread safety**: Interior mutability uses `parking_lot::Mutex` for safe concurrent access
|
||||
|
||||
### What dagx Does NOT Provide
|
||||
|
||||
- **Panic isolation**: Panics in one task will propagate and may affect other tasks (see Panic Handling below)
|
||||
- **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 does NOT isolate panics between tasks. If a task panics:
|
||||
|
||||
- The panic will propagate through the executor
|
||||
- Other tasks in the same execution layer may or may not complete (timing-dependent)
|
||||
- The entire DAG execution will terminate
|
||||
|
||||
**Recommendation**:
|
||||
|
||||
- Use `std::panic::catch_unwind` in tasks that may panic
|
||||
- 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 panic recovery**: Task panics will crash the DAG execution
|
||||
2. **No resource limits**: Tasks can consume unlimited CPU, memory, or time
|
||||
3. **No isolation**: All tasks run in the same process with same privileges
|
||||
4. **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/)
|
||||
- [async-std Security](https://async.rs/security)
|
||||
- [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.
|
||||
Reference in new issue
Block a user