Files
wifi-densepose/vendor/ruvector/docs/adr/delta-behavior/ADR-DB-010-delta-security-model.md

28 KiB

ADR-DB-010: Delta Security Model

Status: Proposed Date: 2026-01-28 Authors: RuVector Architecture Team Deciders: Architecture Review Board, Security Team Parent: ADR-DB-001 Delta Behavior Core Architecture

Version History

Version Date Author Changes
0.1 2026-01-28 Architecture Team Initial proposal

Context and Problem Statement

The Security Challenge

Delta-first architecture introduces new attack surfaces:

  1. Delta Integrity: Deltas could be tampered with in transit or storage
  2. Authorization: Who can create, modify, or read deltas?
  3. Replay Attacks: Resubmission of old deltas
  4. Information Leakage: Delta patterns reveal update frequency
  5. Denial of Service: Flood of malicious deltas

Threat Model

Threat Actor Capability Goal
External Attacker Network access Data exfiltration, corruption
Malicious Insider API access Unauthorized modifications
Compromised Replica Full replica access State corruption
Network Adversary Traffic interception Delta manipulation

Security Requirements

Requirement Priority Description
Integrity Critical Detect tampered deltas
Authentication Critical Verify delta origin
Authorization High Enforce access control
Confidentiality Medium Protect delta contents
Non-repudiation Medium Prove delta authorship
Availability High Resist DoS attacks

Decision

Adopt Signed Deltas with Capability Tokens

We implement a defense-in-depth security model with cryptographically signed deltas and fine-grained capability-based authorization.

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                          SECURITY PERIMETER                                  │
│                                                                             │
│  ┌───────────────┐  ┌───────────────┐  ┌───────────────┐  ┌──────────────┐ │
│  │  TLS 1.3      │  │  mTLS         │  │  Rate Limit   │  │  WAF         │ │
│  │  Transport    │  │  Auth         │  │  (per-client) │  │  (optional)  │ │
│  └───────────────┘  └───────────────┘  └───────────────┘  └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    v
┌─────────────────────────────────────────────────────────────────────────────┐
│                          AUTHENTICATION LAYER                                │
│                                                                             │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │               Identity Verification                                     │ │
│  │   API Key │ JWT │ Client Certificate │ Capability Token                │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    v
┌─────────────────────────────────────────────────────────────────────────────┐
│                          AUTHORIZATION LAYER                                 │
│                                                                             │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────────────────────┐│
│  │  Capability    │  │  RBAC          │  │  Namespace Isolation           ││
│  │  Tokens        │  │  Policies      │  │                                ││
│  └────────────────┘  └────────────────┘  └────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
                                    │
                                    v
┌─────────────────────────────────────────────────────────────────────────────┐
│                          DELTA SECURITY                                      │
│                                                                             │
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────────────────────┐│
│  │  Signature     │  │  Replay        │  │  Integrity                     ││
│  │  Verification  │  │  Protection    │  │  Validation                    ││
│  └────────────────┘  └────────────────┘  └────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘

Core Components

1. Signed Deltas

use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
use sha2::{Sha256, Digest};

/// A cryptographically signed delta
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedDelta {
    /// The delta content
    pub delta: VectorDelta,
    /// Ed25519 signature over delta hash
    pub signature: Signature,
    /// Signing key identifier
    pub key_id: KeyId,
    /// Timestamp of signing
    pub signed_at: DateTime<Utc>,
    /// Nonce for replay protection
    pub nonce: [u8; 16],
}

/// Delta signer for creating signed deltas
pub struct DeltaSigner {
    /// Signing key
    signing_key: SigningKey,
    /// Key identifier
    key_id: KeyId,
    /// Nonce tracker
    nonce_tracker: NonceTracker,
}

impl DeltaSigner {
    /// Sign a delta
    pub fn sign(&self, delta: VectorDelta) -> Result<SignedDelta, SigningError> {
        // Generate nonce
        let nonce = self.nonce_tracker.generate();

        // Create signing payload
        let payload = SigningPayload {
            delta: &delta,
            nonce: &nonce,
            timestamp: Utc::now(),
        };

        // Compute hash
        let hash = self.compute_payload_hash(&payload);

        // Sign hash
        let signature = self.signing_key.sign(&hash);

        Ok(SignedDelta {
            delta,
            signature,
            key_id: self.key_id.clone(),
            signed_at: payload.timestamp,
            nonce,
        })
    }

    fn compute_payload_hash(&self, payload: &SigningPayload) -> [u8; 32] {
        let mut hasher = Sha256::new();

        // Hash delta content
        hasher.update(&bincode::serialize(&payload.delta).unwrap());

        // Hash nonce
        hasher.update(payload.nonce);

        // Hash timestamp
        hasher.update(&payload.timestamp.timestamp().to_le_bytes());

        hasher.finalize().into()
    }
}

/// Delta verifier for validating signed deltas
pub struct DeltaVerifier {
    /// Known public keys
    public_keys: DashMap<KeyId, VerifyingKey>,
    /// Nonce store for replay protection
    nonce_store: NonceStore,
    /// Clock skew tolerance
    clock_tolerance: Duration,
}

impl DeltaVerifier {
    /// Verify a signed delta
    pub fn verify(&self, signed_delta: &SignedDelta) -> Result<(), VerificationError> {
        // Check key exists
        let public_key = self.public_keys
            .get(&signed_delta.key_id)
            .ok_or(VerificationError::UnknownKey)?;

        // Check timestamp is recent
        let age = Utc::now().signed_duration_since(signed_delta.signed_at);
        if age.abs() > self.clock_tolerance.as_secs() as i64 {
            return Err(VerificationError::ExpiredOrFuture);
        }

        // Check nonce hasn't been used
        if self.nonce_store.is_used(&signed_delta.nonce) {
            return Err(VerificationError::ReplayDetected);
        }

        // Verify signature
        let payload = SigningPayload {
            delta: &signed_delta.delta,
            nonce: &signed_delta.nonce,
            timestamp: signed_delta.signed_at,
        };
        let hash = self.compute_payload_hash(&payload);

        public_key.verify(&hash, &signed_delta.signature)
            .map_err(|_| VerificationError::InvalidSignature)?;

        // Mark nonce as used
        self.nonce_store.mark_used(signed_delta.nonce);

        Ok(())
    }
}

2. Capability Tokens

/// Capability token for fine-grained authorization
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapabilityToken {
    /// Token identifier
    pub token_id: TokenId,
    /// Subject (who this token is for)
    pub subject: Subject,
    /// Granted capabilities
    pub capabilities: Vec<Capability>,
    /// Token issuer
    pub issuer: String,
    /// Issued at
    pub issued_at: DateTime<Utc>,
    /// Expires at
    pub expires_at: DateTime<Utc>,
    /// Restrictions
    pub restrictions: TokenRestrictions,
    /// Signature
    pub signature: Signature,
}

/// Individual capability grant
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Capability {
    /// Create deltas for specific vectors
    CreateDelta {
        vector_patterns: Vec<VectorPattern>,
        operation_types: Vec<OperationType>,
    },
    /// Read vectors and their deltas
    ReadVector {
        vector_patterns: Vec<VectorPattern>,
    },
    /// Search capability
    Search {
        namespaces: Vec<String>,
        max_k: usize,
    },
    /// Compact delta chains
    Compact {
        vector_patterns: Vec<VectorPattern>,
    },
    /// Administrative capability
    Admin {
        scope: AdminScope,
    },
}

/// Pattern for matching vector IDs
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VectorPattern {
    /// Exact match
    Exact(VectorId),
    /// Prefix match
    Prefix(String),
    /// Regex match
    Regex(String),
    /// All vectors in namespace
    Namespace(String),
    /// All vectors
    All,
}

/// Token restrictions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenRestrictions {
    /// Rate limit (requests per second)
    pub rate_limit: Option<f32>,
    /// IP address restrictions
    pub allowed_ips: Option<Vec<IpNetwork>>,
    /// Time of day restrictions
    pub time_windows: Option<Vec<TimeWindow>>,
    /// Maximum delta size
    pub max_delta_size: Option<usize>,
}

/// Capability verifier
pub struct CapabilityVerifier {
    /// Trusted issuers' public keys
    issuer_keys: DashMap<String, VerifyingKey>,
    /// Token revocation list
    revoked: HashSet<TokenId>,
}

impl CapabilityVerifier {
    /// Verify token and extract capabilities
    pub fn verify_token(&self, token: &CapabilityToken) -> Result<&[Capability], AuthError> {
        // Check not revoked
        if self.revoked.contains(&token.token_id) {
            return Err(AuthError::TokenRevoked);
        }

        // Check expiration
        if Utc::now() > token.expires_at {
            return Err(AuthError::TokenExpired);
        }

        // Check not before issued
        if Utc::now() < token.issued_at {
            return Err(AuthError::TokenNotYetValid);
        }

        // Verify signature
        let issuer_key = self.issuer_keys
            .get(&token.issuer)
            .ok_or(AuthError::UnknownIssuer)?;

        let payload = self.compute_token_hash(token);
        issuer_key.verify(&payload, &token.signature)
            .map_err(|_| AuthError::InvalidTokenSignature)?;

        Ok(&token.capabilities)
    }

    /// Check if token authorizes an operation
    pub fn authorize(
        &self,
        token: &CapabilityToken,
        operation: &DeltaOperation,
        vector_id: &VectorId,
    ) -> Result<(), AuthError> {
        let capabilities = self.verify_token(token)?;

        for cap in capabilities {
            if self.capability_allows(cap, operation, vector_id) {
                return Ok(());
            }
        }

        Err(AuthError::Unauthorized)
    }

    fn capability_allows(
        &self,
        cap: &Capability,
        operation: &DeltaOperation,
        vector_id: &VectorId,
    ) -> bool {
        match cap {
            Capability::CreateDelta { vector_patterns, operation_types } => {
                // Check vector pattern
                let vector_match = vector_patterns.iter()
                    .any(|p| self.pattern_matches(p, vector_id));

                // Check operation type
                let op_match = operation_types.contains(&operation.operation_type());

                vector_match && op_match
            }
            Capability::Admin { scope: AdminScope::Full } => true,
            _ => false,
        }
    }

    fn pattern_matches(&self, pattern: &VectorPattern, vector_id: &VectorId) -> bool {
        match pattern {
            VectorPattern::Exact(id) => id == vector_id,
            VectorPattern::Prefix(prefix) => vector_id.starts_with(prefix),
            VectorPattern::Regex(re) => {
                regex::Regex::new(re)
                    .map(|r| r.is_match(vector_id))
                    .unwrap_or(false)
            }
            VectorPattern::Namespace(ns) => {
                vector_id.starts_with(&format!("{}:", ns))
            }
            VectorPattern::All => true,
        }
    }
}

3. Rate Limiting and DoS Protection

/// Rate limiter for delta operations
pub struct DeltaRateLimiter {
    /// Per-client limits
    client_limits: DashMap<ClientId, TokenBucket>,
    /// Per-vector limits
    vector_limits: DashMap<VectorId, TokenBucket>,
    /// Global limit
    global_limit: TokenBucket,
    /// Configuration
    config: RateLimitConfig,
}

/// Token bucket for rate limiting
pub struct TokenBucket {
    /// Current tokens
    tokens: AtomicF64,
    /// Last refill time
    last_refill: AtomicU64,
    /// Tokens per second
    rate: f64,
    /// Maximum tokens
    capacity: f64,
}

impl TokenBucket {
    /// Try to consume tokens
    pub fn try_consume(&self, tokens: f64) -> bool {
        // Refill based on elapsed time
        self.refill();

        loop {
            let current = self.tokens.load(Ordering::Relaxed);
            if current < tokens {
                return false;
            }

            if self.tokens.compare_exchange(
                current,
                current - tokens,
                Ordering::SeqCst,
                Ordering::Relaxed,
            ).is_ok() {
                return true;
            }
        }
    }

    fn refill(&self) {
        let now = Instant::now().elapsed().as_millis() as u64;
        let last = self.last_refill.load(Ordering::Relaxed);
        let elapsed = (now - last) as f64 / 1000.0;

        let new_tokens = (self.tokens.load(Ordering::Relaxed) + elapsed * self.rate)
            .min(self.capacity);

        self.tokens.store(new_tokens, Ordering::Relaxed);
        self.last_refill.store(now, Ordering::Relaxed);
    }
}

impl DeltaRateLimiter {
    /// Check if operation is allowed
    pub fn check(&self, client_id: &ClientId, vector_id: &VectorId) -> Result<(), RateLimitError> {
        // Check global limit
        if !self.global_limit.try_consume(1.0) {
            return Err(RateLimitError::GlobalLimitExceeded);
        }

        // Check client limit
        let client_bucket = self.client_limits
            .entry(client_id.clone())
            .or_insert_with(|| TokenBucket::new(
                self.config.client_rate,
                self.config.client_burst,
            ));

        if !client_bucket.try_consume(1.0) {
            return Err(RateLimitError::ClientLimitExceeded);
        }

        // Check vector limit (prevent hot-key abuse)
        let vector_bucket = self.vector_limits
            .entry(vector_id.clone())
            .or_insert_with(|| TokenBucket::new(
                self.config.vector_rate,
                self.config.vector_burst,
            ));

        if !vector_bucket.try_consume(1.0) {
            return Err(RateLimitError::VectorLimitExceeded);
        }

        Ok(())
    }
}

4. Input Validation

/// Delta input validator
pub struct DeltaValidator {
    /// Maximum delta size
    max_delta_size: usize,
    /// Maximum dimensions
    max_dimensions: usize,
    /// Allowed operation types
    allowed_operations: HashSet<OperationType>,
    /// Metadata schema (optional)
    metadata_schema: Option<JsonSchema>,
}

impl DeltaValidator {
    /// Validate a delta before processing
    pub fn validate(&self, delta: &VectorDelta) -> Result<(), ValidationError> {
        // Check delta ID format
        self.validate_id(&delta.delta_id)?;
        self.validate_id(&delta.vector_id)?;

        // Check operation type allowed
        if !self.allowed_operations.contains(&delta.operation.operation_type()) {
            return Err(ValidationError::DisallowedOperation);
        }

        // Validate operation content
        self.validate_operation(&delta.operation)?;

        // Validate metadata if present
        if let Some(metadata) = &delta.metadata_delta {
            self.validate_metadata(metadata)?;
        }

        // Check timestamp is sane
        self.validate_timestamp(delta.timestamp)?;

        Ok(())
    }

    fn validate_id(&self, id: &str) -> Result<(), ValidationError> {
        // Check length
        if id.len() > 256 {
            return Err(ValidationError::IdTooLong);
        }

        // Check for path traversal
        if id.contains("..") || id.contains('/') || id.contains('\\') {
            return Err(ValidationError::InvalidIdChars);
        }

        // Check for null bytes
        if id.contains('\0') {
            return Err(ValidationError::InvalidIdChars);
        }

        Ok(())
    }

    fn validate_operation(&self, op: &DeltaOperation) -> Result<(), ValidationError> {
        match op {
            DeltaOperation::Sparse { indices, values } => {
                // Check arrays have same length
                if indices.len() != values.len() {
                    return Err(ValidationError::MismatchedArrayLengths);
                }

                // Check indices are valid
                for &idx in indices {
                    if idx as usize >= self.max_dimensions {
                        return Err(ValidationError::IndexOutOfBounds);
                    }
                }

                // Check for NaN/Inf values
                for &val in values {
                    if !val.is_finite() {
                        return Err(ValidationError::InvalidValue);
                    }
                }

                // Check total size
                if indices.len() * 8 > self.max_delta_size {
                    return Err(ValidationError::DeltaTooLarge);
                }
            }

            DeltaOperation::Dense { vector } => {
                // Check dimensions
                if vector.len() > self.max_dimensions {
                    return Err(ValidationError::TooManyDimensions);
                }

                // Check for NaN/Inf
                for &val in vector {
                    if !val.is_finite() {
                        return Err(ValidationError::InvalidValue);
                    }
                }

                // Check size
                if vector.len() * 4 > self.max_delta_size {
                    return Err(ValidationError::DeltaTooLarge);
                }
            }

            DeltaOperation::Scale { factor } => {
                if !factor.is_finite() || *factor == 0.0 {
                    return Err(ValidationError::InvalidValue);
                }
            }

            _ => {}
        }

        Ok(())
    }

    fn validate_timestamp(&self, ts: DateTime<Utc>) -> Result<(), ValidationError> {
        let now = Utc::now();
        let age = now.signed_duration_since(ts);

        // Reject timestamps too far in the past (7 days)
        if age.num_days() > 7 {
            return Err(ValidationError::TimestampTooOld);
        }

        // Reject timestamps in the future (with 5 min tolerance)
        if age.num_minutes() < -5 {
            return Err(ValidationError::TimestampInFuture);
        }

        Ok(())
    }
}

Threat Model Analysis

Attack Vectors and Mitigations

Attack Vector Mitigation Residual Risk
Delta tampering Network MitM TLS + signatures Low
Replay attack Network replay Nonces + timestamp Low
Unauthorized access API abuse Capability tokens Low
Data exfiltration Side channels Rate limiting Medium
DoS flooding Request flood Rate limiting Medium
Key compromise Key theft Key rotation Medium
Privilege escalation Token forge Signature verification Low
Input injection Malformed delta Input validation Low

Security Guarantees

Guarantee Mechanism Strength
Integrity Ed25519 signatures Cryptographic
Authentication mTLS + tokens Cryptographic
Authorization Capability tokens Logical
Replay protection Nonces + timestamps Probabilistic
Rate limiting Token buckets Statistical

Considered Options

Option 1: Simple API Keys

Description: Basic API key authentication.

Pros:

  • Simple to implement
  • Easy to understand

Cons:

  • No fine-grained control
  • Key compromise is catastrophic
  • No delta-level security

Verdict: Rejected - insufficient for delta integrity.

Option 2: JWT Tokens

Description: Standard JWT for authentication.

Pros:

  • Industry standard
  • Rich ecosystem

Cons:

  • No per-delta signatures
  • Revocation complexity
  • Limited capability model

Verdict: Partially adopted - used alongside capabilities.

Option 3: Signed Deltas + Capabilities (Selected)

Description: Cryptographic signatures on deltas with capability-based auth.

Pros:

  • Delta-level integrity
  • Fine-grained authorization
  • Non-repudiation
  • Composable security

Cons:

  • Complexity
  • Performance overhead
  • Key management

Verdict: Adopted - provides comprehensive security.

Option 4: Zero-Knowledge Proofs

Description: ZK proofs for privacy-preserving updates.

Pros:

  • Maximum privacy
  • Verifiable computation

Cons:

  • Very complex
  • High overhead
  • Limited tooling

Verdict: Deferred - consider for future privacy features.


Technical Specification

Security Configuration

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
    /// Enable delta signing
    pub signing_enabled: bool,
    /// Signing algorithm
    pub signing_algorithm: SigningAlgorithm,
    /// Enable capability tokens
    pub capabilities_enabled: bool,
    /// Token issuer public keys
    pub trusted_issuers: Vec<TrustedIssuer>,
    /// Rate limiting configuration
    pub rate_limits: RateLimitConfig,
    /// Input validation configuration
    pub validation: ValidationConfig,
    /// Clock skew tolerance
    pub clock_tolerance: Duration,
    /// Nonce window (for replay protection)
    pub nonce_window: Duration,
}

impl Default for SecurityConfig {
    fn default() -> Self {
        Self {
            signing_enabled: true,
            signing_algorithm: SigningAlgorithm::Ed25519,
            capabilities_enabled: true,
            trusted_issuers: vec![],
            rate_limits: RateLimitConfig {
                global_rate: 100_000.0,  // 100K ops/s global
                client_rate: 1000.0,     // 1K ops/s per client
                client_burst: 100.0,
                vector_rate: 100.0,      // 100 ops/s per vector
                vector_burst: 10.0,
            },
            validation: ValidationConfig {
                max_delta_size: 1024 * 1024,  // 1MB
                max_dimensions: 4096,
                max_metadata_size: 65536,
            },
            clock_tolerance: Duration::from_secs(300),  // 5 minutes
            nonce_window: Duration::from_secs(86400),   // 24 hours
        }
    }
}

Wire Format for Signed Delta

Signed Delta Format:
+--------+--------+--------+--------+--------+--------+--------+--------+
| Magic  | Version| Flags  |    Reserved      |    Delta Length         |
| 0x53   | 0x01   |        |                  |    (32-bit LE)          |
+--------+--------+--------+--------+--------+--------+--------+--------+
|                         Delta Payload                                 |
|                    (VectorDelta, encoded)                             |
+-----------------------------------------------------------------------+
|                         Key ID (32 bytes)                             |
+-----------------------------------------------------------------------+
|                     Timestamp (64-bit LE, Unix ms)                    |
+-----------------------------------------------------------------------+
|                         Nonce (16 bytes)                              |
+-----------------------------------------------------------------------+
|                     Signature (64 bytes, Ed25519)                     |
+-----------------------------------------------------------------------+

Flags:
  bit 0: Compressed delta payload
  bit 1: Has capability token attached
  bits 2-7: Reserved

Consequences

Benefits

  1. Integrity: Tamper-proof deltas with cryptographic verification
  2. Authorization: Fine-grained capability-based access control
  3. Auditability: Non-repudiation through signatures
  4. Resilience: DoS protection through rate limiting
  5. Flexibility: Configurable security levels

Risks and Mitigations

Risk Probability Impact Mitigation
Key compromise Low Critical Key rotation, HSM
Performance overhead Medium Medium Batch verification
Configuration errors Medium High Secure defaults
Clock drift Low Medium NTP, tolerance

References

  1. NIST SP 800-63: Digital Identity Guidelines
  2. RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
  3. ADR-DB-001: Delta Behavior Core Architecture
  4. ADR-007: Security Review & Technical Debt

  • ADR-DB-001: Delta Behavior Core Architecture
  • ADR-DB-003: Delta Propagation Protocol
  • ADR-DB-009: Delta Observability
  • ADR-007: Security Review & Technical Debt