Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
682
crates/cognitum-gate-kernel/docs/SECURITY_AUDIT.md
Normal file
682
crates/cognitum-gate-kernel/docs/SECURITY_AUDIT.md
Normal file
@@ -0,0 +1,682 @@
|
||||
# Security Audit Report: Cognitum Gate Implementation
|
||||
|
||||
**Audit Date:** 2026-01-17
|
||||
**Auditor:** Claude Code Security Review Agent
|
||||
**Scope:** cognitum-gate-kernel, cognitum-gate-tilezero, mcp-gate
|
||||
**Risk Classification:** Uses CVSS-style severity (Critical/High/Medium/Low)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This security audit identified **17 security issues** across the cognitum-gate implementation:
|
||||
|
||||
| Severity | Count | Categories |
|
||||
|----------|-------|------------|
|
||||
| Critical | 2 | Cryptographic bypass, signature truncation |
|
||||
| High | 4 | Memory safety, unsafe code, race conditions |
|
||||
| Medium | 6 | Input validation, integer overflow, DoS vectors |
|
||||
| Low | 5 | Information disclosure, edge cases |
|
||||
|
||||
**Recommendation:** The Critical issues in `permit.rs` must be fixed before production deployment as they completely bypass signature verification.
|
||||
|
||||
---
|
||||
|
||||
## Critical Issues
|
||||
|
||||
### CGK-001: Signature Verification Bypass (CRITICAL)
|
||||
|
||||
**Severity:** Critical
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/permit.rs:136-153`
|
||||
**CVSS:** 9.8 (Critical)
|
||||
|
||||
**Description:**
|
||||
The `Verifier::verify()` function does not actually verify signatures. It computes a hash from the token content and compares it to... the same hash computed from the same content. This comparison always succeeds.
|
||||
|
||||
```rust
|
||||
// Lines 147-151 - BROKEN VERIFICATION
|
||||
let expected_hash = blake3::hash(&content);
|
||||
if hash.as_bytes() != expected_hash.as_bytes() {
|
||||
return Err(VerifyError::HashMismatch);
|
||||
}
|
||||
// hash == expected_hash ALWAYS - computed from same content!
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Any attacker can forge permit tokens. The cryptographic authentication is completely bypassed. All gate decisions can be spoofed.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
pub fn verify(&self, token: &PermitToken) -> Result<(), VerifyError> {
|
||||
let content = token.signable_content();
|
||||
let hash = blake3::hash(&content);
|
||||
|
||||
// Reconstruct full 64-byte signature
|
||||
// REQUIRES: Store full signature in token, not truncated 32 bytes
|
||||
let signature = ed25519_dalek::Signature::from_bytes(&token.signature)
|
||||
.map_err(|_| VerifyError::SignatureFailed)?;
|
||||
|
||||
// Actually verify the signature
|
||||
self.verifying_key
|
||||
.verify(hash.as_bytes(), &signature)
|
||||
.map_err(|_| VerifyError::SignatureFailed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-002: Ed25519 Signature Truncation (CRITICAL)
|
||||
|
||||
**Severity:** Critical
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/permit.rs:103-111`
|
||||
**CVSS:** 9.1 (Critical)
|
||||
|
||||
**Description:**
|
||||
The `sign_token` function truncates the 64-byte Ed25519 signature to 32 bytes:
|
||||
|
||||
```rust
|
||||
// Line 109 - Discards half the signature!
|
||||
token.mac.copy_from_slice(&signature.to_bytes()[..32]);
|
||||
```
|
||||
|
||||
Ed25519 signatures are 64 bytes. Truncating to 32 bytes makes reconstruction impossible and verification meaningless.
|
||||
|
||||
**Impact:**
|
||||
Combined with CGK-001, this makes signature verification completely non-functional. Even if verification was fixed, the stored signature cannot be reconstructed.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
// In PermitToken struct - change mac field:
|
||||
pub signature: [u8; 64], // Full Ed25519 signature
|
||||
|
||||
// In sign_token:
|
||||
token.signature.copy_from_slice(&signature.to_bytes());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## High Severity Issues
|
||||
|
||||
### CGK-003: Unsafe Global Mutable State Without Synchronization
|
||||
|
||||
**Severity:** High
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/lib.rs:413`
|
||||
**CVSS:** 7.5
|
||||
|
||||
**Description:**
|
||||
The global `TILE_STATE` is accessed through `static mut` without any synchronization primitives:
|
||||
|
||||
```rust
|
||||
static mut TILE_STATE: Option<TileState> = None;
|
||||
```
|
||||
|
||||
All WASM export functions (`init_tile`, `ingest_delta`, `tick`, etc.) access this mutable static unsafely.
|
||||
|
||||
**Impact:**
|
||||
In multi-threaded contexts or if WASM threading is enabled, this creates data races leading to undefined behavior, memory corruption, or security bypasses.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
use core::cell::UnsafeCell;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
struct TileStateHolder {
|
||||
initialized: AtomicBool,
|
||||
state: UnsafeCell<Option<TileState>>,
|
||||
}
|
||||
|
||||
// Or for single-threaded WASM, use OnceCell pattern
|
||||
static TILE_STATE: once_cell::sync::OnceCell<RefCell<TileState>> = OnceCell::new();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-004: Unsafe Raw Pointer Dereference Without Validation
|
||||
|
||||
**Severity:** High
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/lib.rs:207-210`
|
||||
**CVSS:** 7.3
|
||||
|
||||
**Description:**
|
||||
The `ingest_delta_raw` function casts a raw pointer without checking alignment:
|
||||
|
||||
```rust
|
||||
pub unsafe fn ingest_delta_raw(&mut self, ptr: *const u8) -> bool {
|
||||
let delta = unsafe { &*(ptr as *const Delta) }; // No alignment check!
|
||||
self.ingest_delta(delta)
|
||||
}
|
||||
```
|
||||
|
||||
`Delta` likely requires alignment > 1 byte. Misaligned access is undefined behavior.
|
||||
|
||||
**Impact:**
|
||||
Misaligned memory access causes undefined behavior on some architectures, potentially leading to crashes or exploitable memory corruption.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
pub unsafe fn ingest_delta_raw(&mut self, ptr: *const u8) -> bool {
|
||||
// Check alignment
|
||||
if (ptr as usize) % core::mem::align_of::<Delta>() != 0 {
|
||||
return false;
|
||||
}
|
||||
// Check null
|
||||
if ptr.is_null() {
|
||||
return false;
|
||||
}
|
||||
let delta = unsafe { &*(ptr as *const Delta) };
|
||||
self.ingest_delta(delta)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-005: Bump Allocator Race Condition
|
||||
|
||||
**Severity:** High
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/lib.rs:70-99`
|
||||
**CVSS:** 7.0
|
||||
|
||||
**Description:**
|
||||
The bump allocator uses static mutable variables without synchronization:
|
||||
|
||||
```rust
|
||||
static mut HEAP: [u8; HEAP_SIZE] = [0; HEAP_SIZE];
|
||||
static mut HEAP_PTR: usize = 0;
|
||||
|
||||
unsafe impl GlobalAlloc for BumpAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
unsafe {
|
||||
let aligned = (HEAP_PTR + align - 1) & !(align - 1); // Race condition!
|
||||
// ...
|
||||
HEAP_PTR = aligned + size; // Non-atomic update!
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Concurrent allocations could return overlapping memory regions, leading to memory corruption.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
static HEAP_PTR: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
loop {
|
||||
let current = HEAP_PTR.load(Ordering::Acquire);
|
||||
let aligned = (current + layout.align() - 1) & !(layout.align() - 1);
|
||||
let new_ptr = aligned + layout.size();
|
||||
|
||||
if new_ptr > HEAP_SIZE {
|
||||
return core::ptr::null_mut();
|
||||
}
|
||||
|
||||
if HEAP_PTR.compare_exchange_weak(current, new_ptr, Ordering::Release, Ordering::Relaxed).is_ok() {
|
||||
return unsafe { HEAP.as_mut_ptr().add(aligned) };
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-006: Unchecked Union Access in Delta Processing
|
||||
|
||||
**Severity:** High
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/lib.rs:288-321`
|
||||
**CVSS:** 6.8
|
||||
|
||||
**Description:**
|
||||
The `apply_delta` function uses unsafe union access based on a tag field:
|
||||
|
||||
```rust
|
||||
DeltaTag::EdgeAdd => {
|
||||
let ea = unsafe { delta.get_edge_add() }; // Trusts tag
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
If the tag is corrupted or maliciously set, accessing the wrong union variant leads to undefined behavior.
|
||||
|
||||
**Impact:**
|
||||
A malformed delta with mismatched tag/data could cause memory corruption or information disclosure.
|
||||
|
||||
**Recommended Fix:**
|
||||
- Add validation of delta integrity (checksum/hash)
|
||||
- Use a safe enum representation instead of tagged union where possible
|
||||
- Add bounds checking on union field values after extraction
|
||||
|
||||
---
|
||||
|
||||
## Medium Severity Issues
|
||||
|
||||
### CGK-007: Division by Zero in Threshold Computation
|
||||
|
||||
**Severity:** Medium
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/decision.rs:223-228`
|
||||
**CVSS:** 5.9
|
||||
|
||||
**Description:**
|
||||
Pre-computed reciprocals can cause division by zero:
|
||||
|
||||
```rust
|
||||
let inv_min_cut = 1.0 / thresholds.min_cut; // Zero if min_cut == 0
|
||||
let inv_max_shift = 1.0 / thresholds.max_shift; // Zero if max_shift == 0
|
||||
let inv_tau_range = 1.0 / (thresholds.tau_permit - thresholds.tau_deny); // Zero if equal
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Results in infinity/NaN values that propagate through decision logic, potentially causing incorrect permit/deny decisions.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
pub fn new(thresholds: GateThresholds) -> Result<Self, ThresholdError> {
|
||||
if thresholds.min_cut == 0.0 || thresholds.max_shift == 0.0 {
|
||||
return Err(ThresholdError::ZeroThreshold);
|
||||
}
|
||||
if (thresholds.tau_permit - thresholds.tau_deny).abs() < f64::EPSILON {
|
||||
return Err(ThresholdError::EqualTauRange);
|
||||
}
|
||||
// ... continue with safe reciprocal computation
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-008: Integer Overflow in Token TTL Check
|
||||
|
||||
**Severity:** Medium
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/permit.rs:31-33`
|
||||
**CVSS:** 5.3
|
||||
|
||||
**Description:**
|
||||
The validity check can overflow:
|
||||
|
||||
```rust
|
||||
pub fn is_valid_time(&self, now_ns: u64) -> bool {
|
||||
now_ns <= self.timestamp + self.ttl_ns // Overflow possible!
|
||||
}
|
||||
```
|
||||
|
||||
If `timestamp + ttl_ns` overflows u64, the comparison becomes incorrect.
|
||||
|
||||
**Impact:**
|
||||
Tokens with very large timestamps or TTLs could have incorrect validity checks, either expiring immediately or never expiring.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
pub fn is_valid_time(&self, now_ns: u64) -> bool {
|
||||
self.timestamp.checked_add(self.ttl_ns)
|
||||
.map(|expiry| now_ns <= expiry)
|
||||
.unwrap_or(true) // If overflow, consider perpetually valid or use saturating
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-009: Unbounded History Growth / DoS Vector
|
||||
|
||||
**Severity:** Medium
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/receipt.rs:124-132, 169-185`
|
||||
**CVSS:** 5.0
|
||||
|
||||
**Description:**
|
||||
The `ReceiptLog` uses a HashMap that grows unboundedly:
|
||||
|
||||
```rust
|
||||
pub struct ReceiptLog {
|
||||
receipts: HashMap<u64, WitnessReceipt>, // Grows forever
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, `verify_chain_to` iterates from 0 to sequence number, making it O(n) in chain length.
|
||||
|
||||
**Impact:**
|
||||
Memory exhaustion attack by generating many decisions. Chain verification becomes increasingly slow.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
const MAX_RECEIPTS: usize = 100_000;
|
||||
|
||||
pub fn append(&mut self, receipt: WitnessReceipt) -> Result<(), LogFullError> {
|
||||
if self.receipts.len() >= MAX_RECEIPTS {
|
||||
// Implement pruning or return error
|
||||
self.prune_old_receipts();
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
// Use rolling window verification instead of full chain
|
||||
pub fn verify_recent(&self, window: usize) -> Result<(), ChainVerifyError> {
|
||||
let start = self.latest_sequence.saturating_sub(window as u64);
|
||||
// Verify only recent entries
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-010: Unchecked Array Index in Evidence Processing
|
||||
|
||||
**Severity:** Medium
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/evidence.rs:407-416`
|
||||
**CVSS:** 4.8
|
||||
|
||||
**Description:**
|
||||
Window access uses unchecked indexing:
|
||||
|
||||
```rust
|
||||
let idx = self.window_head as usize;
|
||||
// Line 410: Assumes idx < WINDOW_SIZE
|
||||
unsafe {
|
||||
*self.window.get_unchecked_mut(idx) = ObsRecord { obs, tick };
|
||||
}
|
||||
```
|
||||
|
||||
The bit masking on line 413 is correct, but it happens AFTER the unsafe access.
|
||||
|
||||
**Impact:**
|
||||
If `window_head` is corrupted, out-of-bounds write occurs.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
// Apply mask BEFORE access
|
||||
let idx = (self.window_head as usize) & (WINDOW_SIZE - 1);
|
||||
self.window[idx] = ObsRecord { obs, tick }; // Safe bounds-checked access
|
||||
self.window_head = (self.window_head + 1) as u16;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-011: Panic on System Time Before Epoch
|
||||
|
||||
**Severity:** Medium
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/lib.rs:173-176`
|
||||
**CVSS:** 4.5
|
||||
|
||||
**Description:**
|
||||
The time computation can panic:
|
||||
|
||||
```rust
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap() // Panics if system time < epoch!
|
||||
.as_nanos() as u64;
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
If system time is misconfigured (before 1970), the gate panics and becomes unavailable.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or(std::time::Duration::ZERO)
|
||||
.as_nanos() as u64;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-012: Processing Rate Division by Zero
|
||||
|
||||
**Severity:** Medium
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/report.rs:284-289`
|
||||
**CVSS:** 4.0
|
||||
|
||||
**Description:**
|
||||
```rust
|
||||
pub fn processing_rate(&self) -> f32 {
|
||||
if self.tick_time_us == 0 {
|
||||
0.0 // Handled correctly
|
||||
} else {
|
||||
(self.deltas_processed as f32) / (self.tick_time_us as f32)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is actually handled correctly. However, the check should use floating point division behavior documentation.
|
||||
|
||||
**Status:** No action required - correctly implemented.
|
||||
|
||||
---
|
||||
|
||||
## Low Severity Issues
|
||||
|
||||
### CGK-013: Tick Time Truncation
|
||||
|
||||
**Severity:** Low
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/lib.rs:257`
|
||||
**CVSS:** 3.5
|
||||
|
||||
**Description:**
|
||||
Tick time is cast from u32 to u16:
|
||||
|
||||
```rust
|
||||
report.tick_time_us = (tick_end - tick_start) as u16; // Truncates if > 65535
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Ticks longer than ~65ms will have incorrect timing metrics, affecting performance analysis.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
report.tick_time_us = (tick_end - tick_start).min(u16::MAX as u32) as u16;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-014: Silent JSON Serialization Failure
|
||||
|
||||
**Severity:** Low
|
||||
**Location:** `/home/user/ruvector/crates/cognitum-gate-tilezero/src/receipt.rs:82-83`
|
||||
**CVSS:** 3.1
|
||||
|
||||
**Description:**
|
||||
```rust
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
let json = serde_json::to_vec(self).unwrap_or_default(); // Silent failure!
|
||||
*blake3::hash(&json).as_bytes()
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
If serialization fails, an empty hash is computed, potentially causing hash collisions.
|
||||
|
||||
**Recommended Fix:**
|
||||
```rust
|
||||
pub fn hash(&self) -> Result<[u8; 32], HashError> {
|
||||
let json = serde_json::to_vec(self)?;
|
||||
Ok(*blake3::hash(&json).as_bytes())
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CGK-015: Information Disclosure in Error Messages
|
||||
|
||||
**Severity:** Low
|
||||
**Location:** `/home/user/ruvector/crates/mcp-gate/src/tools.rs:292-355`
|
||||
**CVSS:** 3.0
|
||||
|
||||
**Description:**
|
||||
Error messages expose internal state details:
|
||||
|
||||
```rust
|
||||
format!("Min-cut {:.3} below threshold {:.3}", mincut_value, self.thresholds.min_cut)
|
||||
format!("E-value {:.4} indicates strong evidence of incoherence", summary.evidential.e_value)
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Exposes exact threshold values and internal metrics to clients, aiding targeted attacks.
|
||||
|
||||
**Recommended Fix:**
|
||||
Return generic error codes to external clients; log detailed messages internally only.
|
||||
|
||||
---
|
||||
|
||||
### CGK-016: No Input Size Limits on Tool Calls
|
||||
|
||||
**Severity:** Low
|
||||
**Location:** `/home/user/ruvector/crates/mcp-gate/src/tools.rs:126-159`
|
||||
**CVSS:** 2.8
|
||||
|
||||
**Description:**
|
||||
The `call_tool` function deserializes JSON without size limits:
|
||||
|
||||
```rust
|
||||
let request: PermitActionRequest = serde_json::from_value(call.arguments)
|
||||
.map_err(|e| McpError::InvalidRequest(e.to_string()))?;
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Very large JSON payloads could cause memory exhaustion.
|
||||
|
||||
**Recommended Fix:**
|
||||
Add a size limit check before deserialization or use `serde_json` with size limits.
|
||||
|
||||
---
|
||||
|
||||
### CGK-017: Hardcoded Escalation Timeout
|
||||
|
||||
**Severity:** Low
|
||||
**Location:** `/home/user/ruvector/crates/mcp-gate/src/tools.rs:194`
|
||||
**CVSS:** 2.5
|
||||
|
||||
**Description:**
|
||||
```rust
|
||||
timeout_ns: 300_000_000_000, // 5 minutes - hardcoded
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
Cannot adjust escalation timeout without code changes; not a direct security issue but affects operational security.
|
||||
|
||||
**Recommended Fix:**
|
||||
Make configurable via `GateThresholds` or environment variable.
|
||||
|
||||
---
|
||||
|
||||
## Recommendations Summary
|
||||
|
||||
### Immediate Actions (Critical/High)
|
||||
|
||||
1. **Fix signature verification** (CGK-001, CGK-002) - This is a complete authentication bypass
|
||||
2. **Add synchronization to global state** (CGK-003, CGK-005) - Prevents data races
|
||||
3. **Add alignment/null checks to raw pointer operations** (CGK-004)
|
||||
4. **Add validation to delta processing** (CGK-006)
|
||||
|
||||
### Short-term Actions (Medium)
|
||||
|
||||
5. **Validate thresholds before computing reciprocals** (CGK-007)
|
||||
6. **Use checked arithmetic for token TTL** (CGK-008)
|
||||
7. **Bound receipt log size and optimize chain verification** (CGK-009)
|
||||
8. **Reorder bit masking in evidence window** (CGK-010)
|
||||
9. **Handle system time edge cases** (CGK-011)
|
||||
|
||||
### Long-term Actions (Low)
|
||||
|
||||
10. **Sanitize error messages for external clients** (CGK-015)
|
||||
11. **Add input size limits** (CGK-016)
|
||||
12. **Make operational parameters configurable** (CGK-017)
|
||||
|
||||
---
|
||||
|
||||
## Unsafe Code Audit Summary
|
||||
|
||||
| File | Unsafe Blocks | Safety Concerns |
|
||||
|------|---------------|-----------------|
|
||||
| kernel/lib.rs | 8 | Global state, raw pointers, union access |
|
||||
| kernel/shard.rs | 14 | Unchecked array indexing (performance-critical) |
|
||||
| kernel/evidence.rs | 4 | Unchecked window access |
|
||||
| kernel/report.rs | 0 | None |
|
||||
| tilezero/lib.rs | 0 | None |
|
||||
| tilezero/permit.rs | 0 | None (but cryptographic issues) |
|
||||
| tilezero/receipt.rs | 0 | None |
|
||||
| tilezero/decision.rs | 0 | None |
|
||||
| mcp-gate/tools.rs | 0 | None |
|
||||
|
||||
The kernel crate uses unsafe code extensively for performance optimization. Each instance should be audited against its safety invariants.
|
||||
|
||||
---
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
1. **Fuzzing:** Apply `cargo-fuzz` to delta parsing and token decoding
|
||||
2. **Property testing:** Use `proptest` for invariant validation
|
||||
3. **Miri:** Run `cargo miri test` to detect undefined behavior
|
||||
4. **Memory sanitizers:** Test with AddressSanitizer and MemorySanitizer
|
||||
|
||||
---
|
||||
|
||||
## Compliance Notes
|
||||
|
||||
- **No timing attacks identified** in the cryptographic code (uses constant-time libraries)
|
||||
- **Key generation** uses `OsRng` which is cryptographically secure
|
||||
- **Hash function** (blake3) is modern and appropriate
|
||||
- **Signature scheme** (Ed25519) is appropriate but implementation is broken
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Delta Module Analysis
|
||||
|
||||
**File:** `/home/user/ruvector/crates/cognitum-gate-kernel/src/delta.rs`
|
||||
|
||||
The delta module implements a tagged union (`DeltaPayload`) for graph updates. The design is sound but has some security considerations:
|
||||
|
||||
### Union Safety
|
||||
|
||||
The `DeltaPayload` union is correctly sized (8 bytes for all variants) with compile-time assertions. The unsafe accessor methods (`get_edge_add`, `get_edge_remove`, etc.) correctly require the caller to verify the tag before access.
|
||||
|
||||
**Current Implementation (Lines 379-401):**
|
||||
```rust
|
||||
/// Get the edge add payload (unsafe: caller must verify tag)
|
||||
pub unsafe fn get_edge_add(&self) -> &EdgeAdd {
|
||||
unsafe { &self.payload.edge_add }
|
||||
}
|
||||
```
|
||||
|
||||
**Recommendation:** Consider adding debug assertions:
|
||||
```rust
|
||||
#[inline]
|
||||
pub unsafe fn get_edge_add(&self) -> &EdgeAdd {
|
||||
debug_assert_eq!(self.tag, DeltaTag::EdgeAdd, "Invalid tag for EdgeAdd access");
|
||||
unsafe { &self.payload.edge_add }
|
||||
}
|
||||
```
|
||||
|
||||
### Alignment Considerations
|
||||
|
||||
The `Delta` struct is aligned to 16 bytes (`#[repr(C, align(16))]`), which is correct for WASM and most architectures. However, when deserializing from raw bytes (as in `ingest_delta_raw`), alignment must be verified.
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: Threat Model Summary
|
||||
|
||||
| Threat | Likelihood | Impact | Mitigation Status |
|
||||
|--------|------------|--------|------------------|
|
||||
| Token forgery (CGK-001/002) | High | Critical | NOT MITIGATED |
|
||||
| Memory corruption via malformed delta | Medium | High | Partial (tag check, no integrity check) |
|
||||
| DoS via memory exhaustion | Medium | Medium | Partial (fixed buffers, but unbounded log) |
|
||||
| Race condition exploitation | Low | High | NOT MITIGATED (single-threaded WASM assumed) |
|
||||
| Timing side-channel | Low | Low | Mitigated (constant-time crypto libs) |
|
||||
|
||||
---
|
||||
|
||||
## Appendix C: Verification Status of Unsafe Code Invariants
|
||||
|
||||
| Location | Invariant | Verified By |
|
||||
|----------|-----------|-------------|
|
||||
| shard.rs:450 | source < MAX_SHARD_VERTICES | Bounds check at line 445 |
|
||||
| shard.rs:457 | degree <= MAX_DEGREE | Struct invariant (add_edge checks) |
|
||||
| shard.rs:576-577 | root < MAX_SHARD_VERTICES | Loop construction |
|
||||
| evidence.rs:410 | idx < WINDOW_SIZE | **BROKEN** - mask applied after access |
|
||||
| lib.rs:208 | ptr aligned to Delta alignment | **NOT VERIFIED** |
|
||||
| lib.rs:292 | tag matches payload variant | Tag set during construction only |
|
||||
|
||||
---
|
||||
|
||||
*Report generated by Claude Code Security Review Agent*
|
||||
*Classification: Internal Security Document*
|
||||
Reference in New Issue
Block a user