pulsehive-db 0.6.0

Embedded database for agentic AI systems — collective memory for multi-agent coordination
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# PulseDB: Security Model

> **Version:** 1.0.0  
> **Status:** Approved  
> **Last Updated:** February 2026  
> **Owner:** PulseDB Team

---

## 1. Overview

This document defines the security model for PulseDB, including threat model, trust boundaries, data protection measures, and security testing strategy.

### 1.1 Security Philosophy

PulseDB follows the principle of **minimal attack surface**:
- Embedded library, no network exposure
- No authentication/authorization (consumer responsibility)
- Input validation and size limits
- Fail-safe defaults

### 1.2 Security Scope

| In Scope | Out of Scope |
|----------|--------------|
| Data integrity | Authentication |
| Collective isolation | Authorization |
| Input validation | Network security |
| Resource limits | Encryption at rest |
| Crash safety | Access control lists |

---

## 2. Threat Model

### 2.1 Trust Boundaries

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                           TRUST BOUNDARIES                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │                    CONSUMER APPLICATION                              │    │
│  │                       (Trusted)                                      │    │
│  │                                                                      │    │
│  │  Responsibilities:                                                   │    │
│  │  • User authentication                                               │    │
│  │  • User authorization                                                │    │
│  │  • Input sanitization (content meaning)                              │    │
│  │  • Rate limiting                                                     │    │
│  │  • UserId generation and validation                                  │    │
│  │  • CollectiveId access control                                       │    │
│  │                                                                      │    │
│  └──────────────────────────────┬──────────────────────────────────────┘    │
│                                 │                                           │
│                          PulseDB API                                        │
│                                 │                                           │
│  ┌──────────────────────────────▼──────────────────────────────────────┐    │
│  │                         PULSEDB                                      │    │
│  │                      (Semi-Trusted)                                  │    │
│  │                                                                      │    │
│  │  Responsibilities:                                                   │    │
│  │  • Data integrity                                                    │    │
│  │  • Collective isolation (by ID)                                      │    │
│  │  • Input validation (format, size)                                   │    │
│  │  • Resource limits                                                   │    │
│  │  • Crash recovery                                                    │    │
│  │                                                                      │    │
│  └──────────────────────────────┬──────────────────────────────────────┘    │
│                                 │                                           │
│                          Storage Layer                                      │
│                                 │                                           │
│  ┌──────────────────────────────▼──────────────────────────────────────┐    │
│  │                      FILE SYSTEM                                     │    │
│  │                       (Untrusted)                                    │    │
│  │                                                                      │    │
│  │  Threats:                                                            │    │
│  │  • Unauthorized file access                                          │    │
│  │  • File tampering                                                    │    │
│  │  • Disk corruption                                                   │    │
│  │                                                                      │    │
│  │  Mitigations (OS/Consumer):                                          │    │
│  │  • File permissions                                                  │    │
│  │  • Disk encryption                                                   │    │
│  │  • Backups                                                           │    │
│  │                                                                      │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

### 2.2 Threat Categories

#### 2.2.1 Malformed Input (STRIDE: Tampering)

| Threat | Vector | Impact | Mitigation |
|--------|--------|--------|------------|
| Oversized content | Large content string | Memory exhaustion | Content size limit (100KB) |
| Invalid embedding | Wrong dimension vector | Search corruption | Dimension validation |
| Invalid UTF-8 | Malformed string | Crash/undefined behavior | UTF-8 validation |
| Negative values | importance < 0 | Logic errors | Range validation |
| Self-referential relation | source_id == target_id | Graph corruption | Validation |

#### 2.2.2 Resource Exhaustion (STRIDE: Denial of Service)

| Threat | Vector | Impact | Mitigation |
|--------|--------|--------|------------|
| Memory exhaustion | Many large experiences | OOM crash | Size limits, lazy loading |
| Disk exhaustion | Unlimited writes | Disk full | Configurable quotas |
| CPU exhaustion | Complex searches | Unresponsive | Query timeouts |
| File handle exhaustion | Many opens | System limit | Connection pooling |
| Lock starvation | Long write transactions | Read blocking | Transaction timeouts |

#### 2.2.3 Data Corruption (STRIDE: Tampering)

| Threat | Vector | Impact | Mitigation |
|--------|--------|--------|------------|
| Partial writes | Crash during write | Inconsistent state | Atomic transactions |
| Index/data mismatch | Crash after redb, before HNSW | Orphan vectors | Write ordering |
| Bit rot | Storage degradation | Silent corruption | Checksums |
| Concurrent writes | Race conditions | Data loss | Single-writer model |

#### 2.2.4 Information Disclosure (STRIDE: Information Disclosure)

| Threat | Vector | Impact | Mitigation |
|--------|--------|--------|------------|
| Cross-collective leak | CollectiveId guessing | Privacy violation | CollectiveId in all queries |
| Error message leak | Detailed errors | Path disclosure | Generic error messages |
| Memory disclosure | Buffer over-read | Data leak | Rust memory safety |
| Timing attacks | Search latency variance | Existence inference | N/A (accepted risk) |

### 2.3 Out-of-Scope Threats

These threats are explicitly NOT mitigated by PulseDB:

| Threat | Reason | Consumer Responsibility |
|--------|--------|------------------------|
| Unauthorized access | No auth layer | Implement access control |
| Network attacks | Embedded, no network | N/A or wrap in secure service |
| Encryption at rest | Performance/complexity | Use disk encryption |
| Privilege escalation | Same process | OS-level isolation |
| Malicious consumer | Library is trusted | N/A |

---

## 3. Security Controls

### 3.1 Input Validation

```rust
impl NewExperience {
    pub fn validate(&self) -> Result<(), ValidationError> {
        // Content validation
        if self.content.is_empty() {
            return Err(ValidationError::InvalidField {
                field: "content".into(),
                reason: "cannot be empty".into(),
            });
        }
        if self.content.len() > MAX_CONTENT_SIZE {
            return Err(ValidationError::ContentTooLarge {
                size: self.content.len(),
                max: MAX_CONTENT_SIZE,
            });
        }
        
        // Score validation
        if self.importance < 0.0 || self.importance > 1.0 {
            return Err(ValidationError::InvalidField {
                field: "importance".into(),
                reason: "must be between 0.0 and 1.0".into(),
            });
        }
        
        // Domain validation
        if self.domain.len() > MAX_DOMAIN_TAGS {
            return Err(ValidationError::InvalidField {
                field: "domain".into(),
                reason: format!("max {} tags allowed", MAX_DOMAIN_TAGS),
            });
        }
        for tag in &self.domain {
            if tag.len() > MAX_TAG_LENGTH {
                return Err(ValidationError::InvalidField {
                    field: "domain".into(),
                    reason: format!("tag too long: max {} chars", MAX_TAG_LENGTH),
                });
            }
        }
        
        Ok(())
    }
}

// Size limits
const MAX_CONTENT_SIZE: usize = 100 * 1024;      // 100 KB
const MAX_DOMAIN_TAGS: usize = 50;
const MAX_TAG_LENGTH: usize = 100;
const MAX_FILES: usize = 100;
const MAX_FILE_PATH_LENGTH: usize = 500;
const MAX_METADATA_SIZE: usize = 10 * 1024;      // 10 KB
```

### 3.2 Embedding Validation

```rust
impl PulseDB {
    fn validate_embedding(
        &self,
        collective_id: CollectiveId,
        embedding: &[f32],
    ) -> Result<(), ValidationError> {
        let collective = self.get_collective(collective_id)?
            .ok_or(ValidationError::CollectiveNotFound(collective_id))?;
        
        // Dimension check
        if embedding.len() != collective.embedding_dimension {
            return Err(ValidationError::DimensionMismatch {
                expected: collective.embedding_dimension,
                got: embedding.len(),
            });
        }
        
        // NaN/Inf check
        for (i, &v) in embedding.iter().enumerate() {
            if v.is_nan() || v.is_infinite() {
                return Err(ValidationError::InvalidField {
                    field: "embedding".into(),
                    reason: format!("invalid value at index {}: {}", i, v),
                });
            }
        }
        
        Ok(())
    }
}
```

### 3.3 Collective Isolation

```rust
impl PulseDB {
    // All queries MUST include collective_id
    pub fn search_similar(
        &self,
        collective_id: CollectiveId,  // Required, not optional
        query: &[f32],
        k: usize,
    ) -> Result<Vec<(Experience, f32)>> {
        // Validate collective exists
        self.validate_collective_exists(collective_id)?;
        
        // Search only within collective's HNSW index
        let index = self.get_hnsw_index(collective_id)?;
        let results = index.search(query, k)?;
        
        // Filter results to ensure collective isolation
        let experiences: Vec<_> = results
            .into_iter()
            .filter_map(|(id, score)| {
                let exp = self.get_experience(id).ok()??;
                // Double-check collective (defense in depth)
                if exp.collective_id == collective_id {
                    Some((exp, score))
                } else {
                    log::error!("Cross-collective leak prevented: {:?}", id);
                    None
                }
            })
            .collect();
        
        Ok(experiences)
    }
}
```

### 3.4 Resource Limits

```rust
#[derive(Clone, Debug)]
pub struct ResourceLimits {
    /// Maximum experiences per collective
    pub max_experiences_per_collective: Option<u64>,
    
    /// Maximum total storage bytes
    pub max_storage_bytes: Option<u64>,
    
    /// Maximum concurrent read transactions
    pub max_concurrent_reads: usize,
    
    /// Transaction timeout
    pub transaction_timeout: Duration,
    
    /// Query timeout
    pub query_timeout: Duration,
}

impl Default for ResourceLimits {
    fn default() -> Self {
        Self {
            max_experiences_per_collective: None,  // Unlimited by default
            max_storage_bytes: None,
            max_concurrent_reads: 100,
            transaction_timeout: Duration::from_secs(30),
            query_timeout: Duration::from_secs(10),
        }
    }
}

impl PulseDB {
    fn check_resource_limits(&self, collective_id: CollectiveId) -> Result<()> {
        if let Some(max) = self.limits.max_experiences_per_collective {
            let stats = self.get_collective_stats(collective_id)?;
            if stats.experience_count >= max {
                return Err(PulseDBError::ResourceLimit(
                    "Maximum experiences per collective exceeded".into()
                ));
            }
        }
        
        if let Some(max) = self.limits.max_storage_bytes {
            let total = self.get_total_storage_bytes()?;
            if total >= max {
                return Err(PulseDBError::ResourceLimit(
                    "Maximum storage limit exceeded".into()
                ));
            }
        }
        
        Ok(())
    }
}
```

### 3.5 Error Handling

```rust
impl PulseDBError {
    /// Convert to user-safe message (no internal details)
    pub fn user_message(&self) -> &'static str {
        match self {
            Self::Storage(_) => "A storage error occurred",
            Self::Validation(_) => "Invalid input provided",
            Self::NotFound(_) => "Resource not found",
            Self::Concurrency(_) => "Operation could not complete, please retry",
            Self::Embedding(_) => "Embedding generation failed",
            Self::ResourceLimit(_) => "Resource limit exceeded",
        }
    }
    
    /// Check if error should be logged with full details
    pub fn should_log_details(&self) -> bool {
        matches!(self, Self::Storage(_) | Self::Concurrency(_))
    }
}

// Safe error handling pattern
fn handle_api_error(error: PulseDBError) -> ApiResponse {
    if error.should_log_details() {
        log::error!("PulseDB error: {:?}", error);  // Full details to logs
    }
    
    ApiResponse::error(error.user_message())  // Safe message to user
}
```

---

## 4. Data Protection

### 4.1 Data at Rest

| Protection | Status | Notes |
|------------|--------|-------|
| Encryption | ❌ Not provided | Use OS disk encryption |
| Checksums | ✅ redb provides | Detect corruption |
| File permissions | ❌ Consumer responsibility | Set 0600 |

**Recommended Consumer Configuration:**
```bash
# Set restrictive permissions on database files
chmod 600 pulse.db
chmod 600 pulse.db.hnsw/*
chmod 700 pulse.db.hnsw/
```

### 4.2 Data in Memory

| Protection | Status | Notes |
|------------|--------|-------|
| Memory safety | ✅ Rust guarantees | No buffer overflows |
| Secure zeroing | ❌ Not provided | Use `zeroize` if needed |
| Core dumps | ❌ Consumer responsibility | Disable in production |

### 4.3 Data Deletion

```rust
impl PulseDB {
    /// Securely delete an experience (best-effort)
    pub fn secure_delete_experience(&self, id: ExperienceId) -> Result<()> {
        // 1. Remove from HNSW index
        self.hnsw.remove(id)?;
        
        // 2. Delete from storage
        self.storage.delete(id)?;
        
        // 3. Delete relations
        self.delete_relations_for_experience(id)?;
        
        // Note: Data may persist in:
        // - redb free pages (until compaction)
        // - HNSW tombstones (until rebuild)
        // - File system (until overwrite)
        // For true secure deletion, use encrypted storage
        // and destroy key
        
        Ok(())
    }
    
    /// Compact database to reclaim deleted space
    pub fn compact(&self) -> Result<()> {
        self.storage.compact()?;
        Ok(())
    }
}
```

---

## 5. Concurrency Security

### 5.1 Single-Writer Model

```rust
impl PulseDB {
    // Write lock prevents concurrent writes
    fn with_write_lock<F, R>(&self, f: F) -> Result<R>
    where
        F: FnOnce(&mut WriteTxn) -> Result<R>,
    {
        // Acquire write lock with timeout
        let guard = self.write_lock
            .try_lock_for(self.limits.transaction_timeout)
            .ok_or(PulseDBError::Concurrency(
                ConcurrencyError::LockTimeout
            ))?;
        
        let mut txn = self.storage.begin_write()?;
        let result = f(&mut txn)?;
        txn.commit()?;
        
        drop(guard);
        Ok(result)
    }
}
```

### 5.2 Cross-Process Safety

```rust
// File-based lock for cross-process coordination
impl PulseDB {
    fn acquire_file_lock(path: &Path) -> Result<FileLock> {
        let lock_path = path.with_extension("db.lock");
        let file = File::create(&lock_path)?;
        
        // Try exclusive lock with timeout
        let start = Instant::now();
        loop {
            match file.try_lock_exclusive() {
                Ok(_) => return Ok(FileLock { file, path: lock_path }),
                Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
                    if start.elapsed() > Duration::from_secs(30) {
                        return Err(PulseDBError::Concurrency(
                            ConcurrencyError::FileLockTimeout
                        ));
                    }
                    std::thread::sleep(Duration::from_millis(100));
                }
                Err(e) => return Err(e.into()),
            }
        }
    }
}
```

---

## 6. Security Testing

### 6.1 Fuzz Testing

```rust
// fuzz/fuzz_targets/experience.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
use arbitrary::Arbitrary;
use pulsedb::{PulseDB, NewExperience};

#[derive(Arbitrary, Debug)]
struct FuzzInput {
    content: String,
    importance: f32,
    confidence: f32,
    domain: Vec<String>,
    embedding: Vec<f32>,
}

fuzz_target!(|input: FuzzInput| {
    let db = setup_temp_db();
    let collective_id = db.create_collective("fuzz").unwrap();
    
    // Should never panic, always return Result
    let _ = db.record_experience(NewExperience {
        collective_id,
        content: input.content,
        importance: input.importance,
        confidence: input.confidence,
        domain: input.domain,
        embedding: Some(input.embedding),
        ..Default::default()
    });
});
```

**Fuzz Targets:**
1. `experience` - Experience creation with arbitrary input
2. `search` - Search with arbitrary embeddings
3. `relation` - Relation creation with arbitrary IDs
4. `deserialize` - Deserialize arbitrary bytes

### 6.2 Property-Based Testing

```rust
#[cfg(test)]
mod property_tests {
    use proptest::prelude::*;
    
    proptest! {
        #[test]
        fn experience_roundtrip(
            content in ".*",
            importance in 0.0f32..=1.0f32,
        ) {
            let db = setup_temp_db();
            let collective_id = db.create_collective("test")?;
            
            let id = db.record_experience(NewExperience {
                collective_id,
                content: content.clone(),
                importance,
                ..Default::default()
            })?;
            
            let retrieved = db.get_experience(id)?.unwrap();
            prop_assert_eq!(retrieved.content, content);
            prop_assert!((retrieved.importance - importance).abs() < 0.0001);
        }
        
        #[test]
        fn collective_isolation(
            content_a in ".*",
            content_b in ".*",
        ) {
            let db = setup_temp_db();
            let collective_a = db.create_collective("a")?;
            let collective_b = db.create_collective("b")?;
            
            db.record_experience(NewExperience {
                collective_id: collective_a,
                content: content_a.clone(),
                ..Default::default()
            })?;
            
            db.record_experience(NewExperience {
                collective_id: collective_b,
                content: content_b.clone(),
                ..Default::default()
            })?;
            
            // Search in collective_a should NOT return content_b
            let results = db.search_similar(collective_a, &random_embedding(), 100)?;
            for (exp, _) in results {
                prop_assert_eq!(exp.collective_id, collective_a);
            }
        }
    }
}
```

### 6.3 Security Test Cases

| Test ID | Description | Expected Result |
|---------|-------------|-----------------|
| SEC-001 | Content exceeding 100KB | `ValidationError::ContentTooLarge` |
| SEC-002 | Embedding dimension mismatch | `ValidationError::DimensionMismatch` |
| SEC-003 | NaN in embedding | `ValidationError::InvalidField` |
| SEC-004 | importance > 1.0 | `ValidationError::InvalidField` |
| SEC-005 | importance < 0.0 | `ValidationError::InvalidField` |
| SEC-006 | Self-referential relation | `ValidationError::InvalidField` |
| SEC-007 | Cross-collective relation | `ValidationError::InvalidField` |
| SEC-008 | Non-existent collective | `ValidationError::CollectiveNotFound` |
| SEC-009 | Non-existent experience | `NotFoundError` or `None` |
| SEC-010 | Empty content | `ValidationError::InvalidField` |
| SEC-011 | Concurrent write attempt | `ConcurrencyError::LockTimeout` |
| SEC-012 | Crash during write | Data consistent after restart |
| SEC-013 | Read during write | Read sees consistent snapshot |

---

## 7. Security Recommendations for Consumers

### 7.1 Deployment Checklist

```markdown
## PulseDB Security Deployment Checklist

### File System
- [ ] Database files have restrictive permissions (0600)
- [ ] Database directory has restrictive permissions (0700)
- [ ] Disk encryption enabled (e.g., LUKS, FileVault, BitLocker)
- [ ] Regular backups configured
- [ ] Backup encryption enabled

### Access Control
- [ ] Authentication implemented for API access
- [ ] Authorization checks before PulseDB operations
- [ ] UserId validated before passing to PulseDB
- [ ] CollectiveId access control implemented

### Monitoring
- [ ] Error logging configured (without sensitive data)
- [ ] Resource usage monitoring enabled
- [ ] Anomaly detection for unusual patterns

### Testing
- [ ] Input validation tested
- [ ] Authorization bypass tested
- [ ] Error handling tested
- [ ] Crash recovery tested
```

### 7.2 Secure Integration Pattern

```rust
// Example: Secure wrapper around PulseDB
pub struct SecurePulseDB {
    db: PulseDB,
    auth: AuthService,
}

impl SecurePulseDB {
    pub async fn record_experience(
        &self,
        user_token: &str,
        experience: NewExperience,
    ) -> Result<ExperienceId> {
        // 1. Authenticate user
        let user = self.auth.verify_token(user_token).await?;
        
        // 2. Authorize access to collective
        if !self.auth.can_write(user.id, experience.collective_id).await? {
            return Err(AuthError::Forbidden);
        }
        
        // 3. Sanitize input (application-specific)
        let sanitized = self.sanitize_experience(experience)?;
        
        // 4. Rate limit
        self.auth.check_rate_limit(user.id).await?;
        
        // 5. Record to PulseDB
        let id = self.db.record_experience(sanitized)?;
        
        // 6. Audit log
        self.audit_log(user.id, "record_experience", id).await;
        
        Ok(id)
    }
}
```

---

## 8. Security Non-Goals

These are explicitly NOT security goals for PulseDB v1:

| Non-Goal | Rationale |
|----------|-----------|
| Encryption at rest | Performance cost, OS solutions available |
| Authentication | Consumer responsibility, many valid approaches |
| Authorization | Consumer responsibility, application-specific |
| Audit logging | Consumer responsibility, application-specific |
| Network security | Embedded library, no network exposure |
| Multi-tenancy isolation | Collective isolation is logical, not security boundary |
| Tamper-evident logs | Not a security database |
| Secure multi-party computation | Out of scope |

---

## 9. References

- [OWASP Threat Modeling]https://owasp.org/www-community/Threat_Modeling
- [STRIDE Threat Model]https://docs.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats
- [Rust Security Guidelines]https://rustsec.org/

---

## Changelog

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0.0 | February 2026 | PulseDB Team | Initial security model |