Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dallay/corvus/llms.txt

Use this file to discover all available pages before exploring further.

Corvus implements a defense-in-depth security model with multiple overlapping layers. Security is a first-class concern, built into every component from the ground up.
Security First PhilosophyEvery decision, every line of code, every architecture choice MUST prioritize:
  1. Security First - Always think about attacks, vulnerabilities, and safe defaults
  2. Performance Second - Optimize for efficiency after security
These principles override convenience, speed of development, and “getting it done quickly.”

Security Layers

Corvus provides multiple overlapping security boundaries:
┌───────────────────────────────────────────────────────────┐
│ Layer 5: Gateway Pairing & Authentication                 │
│  • One-time pairing codes                                 │
│  • Bearer token authentication                            │
│  • Rate limiting & brute-force protection                 │
└─────────────────┬─────────────────────────────────────────┘

┌─────────────────▼─────────────────────────────────────────┐
│ Layer 4: Security Policy Engine                           │
│  • Autonomy levels (read-only, supervised, full)          │
│  • Command allowlists & path blocklists                   │
│  • Risk assessment & approval workflows                   │
│  • Rate limiting (max actions per hour)                   │
└─────────────────┬─────────────────────────────────────────┘

┌─────────────────▼─────────────────────────────────────────┐
│ Layer 3: Runtime Sandboxing                               │
│  • Docker container isolation (process, network, fs)      │
│  • Landlock/Firejail (Linux kernel sandboxing)            │
│  • Resource limits (memory, CPU, time)                    │
└─────────────────┬─────────────────────────────────────────┘

┌─────────────────▼─────────────────────────────────────────┐
│ Layer 2: Filesystem & Network Scoping                     │
│  • Workspace-only filesystem access                       │
│  • Forbidden path enforcement                             │
│  • Network isolation (Docker network=none)                │
└─────────────────┬─────────────────────────────────────────┘

┌─────────────────▼─────────────────────────────────────────┐
│ Layer 1: Input Validation & Secret Management             │
│  • Parameter validation & sanitization                    │
│  • Encrypted secret storage                               │
│  • No secrets in logs or audit trails                     │
└───────────────────────────────────────────────────────────┘

Layer 5: Gateway Pairing

The Gateway provides HTTP/WebSocket access to Corvus with mandatory authentication.

Pairing Flow

  1. Startup - Gateway generates a 6-digit pairing code
  2. Display - Code printed to terminal (not logged)
  3. Client pairing - Client sends POST /pair with code
  4. Token generation - Gateway returns bearer token
  5. Authenticated requests - Client includes token in Authorization header
Implementation: clients/agent-runtime/src/security/pairing.rs
pub struct PairingGuard {
    require_pairing: bool,
    pairing_code: Mutex<Option<String>>,
    paired_tokens: Mutex<HashSet<String>>,  // SHA-256 hashes
    failed_attempts: Mutex<(u32, Option<Instant>)>,
}
Key features:
  • One-time pairing codes - 6-digit numeric codes with CSPRNG
  • Token hashing - Tokens stored as SHA-256 hashes
  • Brute-force protection - 5 failed attempts → 5-minute lockout
  • Constant-time comparison - Timing attack resistant
Configuration:
[gateway]
enabled = true
host = "127.0.0.1"      # Bind to localhost only
port = 7777
require_pairing = true  # Enforce pairing
Public Binding WarningIf binding to 0.0.0.0 or a public IP:
  • Pairing MUST be enabled
  • Gateway will refuse to start without pairing
  • Use firewall rules to restrict access

Token Management

Token generation:
fn generate_token() -> String {
    let mut bytes = [0u8; 32];  // 256 bits of entropy
    rand::rng().fill_bytes(&mut bytes);
    format!("zc_{}", hex::encode(bytes))
}
Token storage:
fn hash_token(token: &str) -> String {
    format!("{:x}", Sha256::digest(token.as_bytes()))
}
Token validation:
pub fn is_authenticated(&self, token: &str) -> bool {
    let hashed = hash_token(token);
    let tokens = self.paired_tokens.lock();
    tokens.iter().fold(false, |acc, stored| {
        let equal = constant_time_eq(stored, &hashed);
        acc | equal  // Constant-time OR to prevent early exit
    })
}

Layer 4: Security Policy

The SecurityPolicy defines what agents are allowed to do. Location: clients/agent-runtime/src/security/policy.rs
pub struct SecurityPolicy {
    pub autonomy: AutonomyLevel,
    pub workspace_dir: PathBuf,
    pub workspace_only: bool,
    pub allowed_commands: Vec<String>,
    pub forbidden_paths: Vec<String>,
    pub max_actions_per_hour: u32,
    pub max_cost_per_day_cents: u32,
    pub require_approval_for_medium_risk: bool,
    pub block_high_risk_commands: bool,
    pub tracker: ActionTracker,
}

Autonomy Levels

pub enum AutonomyLevel {
    ReadOnly,    // Observe but not act
    Supervised,  // Act with approval for risky ops (default)
    Full,        // Autonomous within policy bounds
}
Behavior:
OperationReadOnlySupervisedFull
Read files
Write files⚠️ Approval
Shell (low risk)
Shell (medium risk)⚠️ Approval
Shell (high risk)
Network access⚠️ Approval

Command Allowlists

Only explicitly allowed commands can execute:
[security]
allowed_commands = [
  "git", "npm", "cargo",
  "ls", "cat", "grep", "find",
  "echo", "pwd", "wc", "head", "tail"
]
Validation:
fn is_command_allowed(&self, command: &str) -> bool {
    let cmd = command.split_whitespace().next().unwrap_or("");
    self.allowed_commands.iter().any(|allowed| {
        cmd == allowed || cmd.ends_with(&format!("/{}", allowed))
    })
}

Path Blocklists

Critical paths are always forbidden:
forbidden_paths: vec![
    // System directories
    "/etc", "/root", "/home", "/usr", "/bin", "/sbin",
    "/sys", "/proc", "/dev", "/boot", "/var",
    // Sensitive files
    "/etc/passwd", "/etc/shadow", "/etc/sudoers",
    // SSH keys
    "~/.ssh/id_rsa", "~/.ssh/id_ed25519",
],

Risk Assessment

pub fn assess_command_risk(command: &str) -> CommandRiskLevel {
    let lower = command.to_lowercase();
    
    // High risk: destructive operations
    if lower.contains("rm -rf") || lower.contains("dd if=")
        || lower.contains("> /dev/") || lower.contains("mkfs")
        || lower.contains(":(){ :|:& };:")  // Fork bomb
    {
        return CommandRiskLevel::High;
    }
    
    // Medium risk: write operations
    if lower.contains("curl") || lower.contains("wget")
        || lower.contains("sudo") || lower.contains("chmod")
        || lower.contains(">>") || lower.contains(">")
    {
        return CommandRiskLevel::Medium;
    }
    
    // Low risk: read-only operations
    CommandRiskLevel::Low
}

Rate Limiting

Action tracking:
pub struct ActionTracker {
    actions: Mutex<Vec<Instant>>,
}

impl ActionTracker {
    pub fn record(&self) -> usize {
        let mut actions = self.actions.lock();
        let cutoff = Instant::now() - Duration::from_secs(3600);
        actions.retain(|t| *t > cutoff);
        actions.push(Instant::now());
        actions.len()
    }
}
Enforcement:
if policy.tracker.count() >= policy.max_actions_per_hour {
    anyhow::bail!("Rate limit exceeded: {} actions/hour", 
                  policy.max_actions_per_hour);
}

Layer 3: Runtime Sandboxing

See Runtime Model for detailed sandbox information. Docker isolation:
  • Process namespace (--pid)
  • Network namespace (--network none)
  • Mount namespace (read-only rootfs)
  • Resource limits (--memory, --cpus)
Linux kernel sandboxing:
  • Landlock - Filesystem access control
  • Firejail - Namespace isolation + seccomp
  • Bubblewrap - Lightweight containers

Layer 2: Filesystem Scoping

Workspace-Only Mode

[security]
workspace_dir = "/path/to/project"
workspace_only = true  # Restrict all operations to workspace
Enforcement:
fn validate_path(&self, path: &Path) -> anyhow::Result<()> {
    let canonical = path.canonicalize()?;
    
    // Check workspace boundary
    if self.workspace_only && !canonical.starts_with(&self.workspace_dir) {
        anyhow::bail!("Path outside workspace: {}", path.display());
    }
    
    // Check forbidden paths
    for forbidden in &self.forbidden_paths {
        if canonical.starts_with(forbidden) {
            anyhow::bail!("Forbidden path: {}", path.display());
        }
    }
    
    Ok(())
}

Network Isolation

Docker:
[runtime.docker]
network = "none"  # Block all network access
Native: No built-in network isolation - rely on firewall rules.

Layer 1: Input Validation

Parameter Validation

All tool inputs are validated against JSON schemas:
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
    // Validate required fields
    let path = args["path"].as_str()
        .ok_or_else(|| anyhow::anyhow!("Missing 'path' parameter"))?;
    
    // Sanitize path
    let path = PathBuf::from(path);
    if path.to_string_lossy().contains("..") {
        anyhow::bail!("Path traversal attempt detected");
    }
    
    // Validate against policy
    self.policy.validate_path(&path)?;
    
    // Execute operation
    // ...
}

Secret Management

Secrets are encrypted at rest using XChaCha20-Poly1305. Location: clients/agent-runtime/src/security/secrets.rs
pub struct SecretStore {
    storage_dir: PathBuf,
    cipher: XChaCha20Poly1305,
}

impl SecretStore {
    pub fn encrypt(&self, plaintext: &str) -> anyhow::Result<String> {
        let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
        let ciphertext = self.cipher.encrypt(&nonce, plaintext.as_bytes())?;
        Ok(format!("{}:{}", hex::encode(nonce), hex::encode(ciphertext)))
    }
    
    pub fn decrypt(&self, encrypted: &str) -> anyhow::Result<String> {
        let parts: Vec<&str> = encrypted.split(':').collect();
        let nonce = hex::decode(parts[0])?;
        let ciphertext = hex::decode(parts[1])?;
        let plaintext = self.cipher.decrypt(&nonce.into(), ciphertext.as_ref())?;
        Ok(String::from_utf8(plaintext)?)
    }
}
Usage:
let store = SecretStore::new(config_dir, paranoid_mode);
let encrypted = store.encrypt("sk-1234567890")?;
config.api_key = encrypted;
save_config(&config)?;

Logging & Audit

NEVER log sensitive data:
const SENSITIVE_MARKERS: [&str; 5] = [
    "password", "token", "secret", "api_key", "auth"
];

pub fn redact_observer_payload(value: &str) -> String {
    let lower = value.to_ascii_lowercase();
    if SENSITIVE_MARKERS.iter().any(|m| lower.contains(m)) {
        return "***REDACTED***".to_string();
    }
    value.to_string()
}
Audit logging:
pub struct AuditEvent {
    pub timestamp: String,
    pub event_type: AuditEventType,
    pub user: String,
    pub component: String,
    pub action: String,
    pub outcome: String,
    pub details: HashMap<String, String>,
}

Threat Model

Threats We Defend Against

Attack: User tricks agent into executing harmful commandsDefense:
  • Command allowlists
  • Risk assessment
  • Approval workflows for risky operations
  • Sandbox isolation
Attack: Agent accesses files outside workspaceDefense:
  • Path canonicalization
  • Workspace boundary checks
  • Forbidden path enforcement
  • Docker mount restrictions
Attack: Malicious shell metacharacters in parametersDefense:
  • Input validation and sanitization
  • No string concatenation for shell commands
  • Use of Command::arg() (no shell parsing)
  • Sandbox isolation
Attack: Agent consumes excessive CPU/memory/diskDefense:
  • Docker resource limits
  • Rate limiting (actions per hour)
  • Execution timeouts
  • Memory budgets
Attack: Agent leaks API keys or secretsDefense:
  • Encrypted secret storage
  • No secrets in logs
  • Redaction in observability
  • Environment variable isolation
Attack: Attacker sends requests to gatewayDefense:
  • Mandatory pairing on public bind
  • Bearer token authentication
  • Brute-force protection
  • Rate limiting
Attack: Agent breaks out of Docker containerDefense:
  • No privileged containers
  • Read-only root filesystem
  • Minimal Linux capabilities
  • No host mounts of sensitive paths

Threats We Don’t Defend Against

Out of Scope:
  • Physical access to host machine
  • Kernel exploits (rely on OS patches)
  • Side-channel attacks (timing, cache)
  • Social engineering of human operators
  • Supply chain attacks (use dependency scanning)

Security Checklist

Before deploying Corvus to production:
Set autonomy = "supervised" (default)
Enable require_pairing = true for public gateway
Use Docker runtime for untrusted workloads
Enable read_only_rootfs = true in Docker config
Set network = "none" unless network access required
Configure resource limits (memory, CPU)
Set workspace_only = true
Review and minimize allowed_commands
Expand forbidden_paths for your environment
Bind gateway to 127.0.0.1 for localhost only
Use firewall rules to restrict public access
Enable TLS/HTTPS with valid certificates
Use a reverse proxy (nginx, Caddy) in production
Implement rate limiting at proxy level
Enable paranoid_mode = true for secret encryption
Store config files with restrictive permissions (600)
Use environment variables for sensitive config
Never commit secrets to version control
Rotate bearer tokens periodically
Enable audit logging
Monitor failed authentication attempts
Alert on high-risk command executions
Track resource usage (CPU, memory)
Set up security event notifications
Keep Corvus and dependencies updated
Run vulnerability scanning on Docker images
Review audit logs regularly
Test incident response procedures
Document security policies

Best Practices

Principle of Least Privilege

Grant only the minimum permissions required. Start restrictive and relax as needed.

Defense in Depth

Don’t rely on a single security layer. Multiple boundaries increase resilience.

Secure by Default

Default configuration should be secure. Require explicit opt-in for risky features.

Fail Securely

On error, fail closed (deny access) not open (allow access).

Security Research

We welcome responsible security research:
Report vulnerabilities to: security@corvus.devPlease include:
  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact
  • Suggested fix (if applicable)
We aim to respond within 48 hours.

Next Steps

Runtime Model

Understand sandboxing and isolation

Configuration

Configure security policies

Deployment Guide

Deploy securely to production

Architecture

See how security fits into the system

Build docs developers (and LLMs) love