Back to Ruview

ruv-neural Crate System: Security and Performance Review

rust-port/wifi-densepose-rs/crates/ruv-neural/SECURITY_REVIEW.md

0.7.029.1 KB
Original Source

ruv-neural Crate System: Security and Performance Review

Date: 2026-03-09 Version: 0.1.0 Scope: All 12 workspace crates in the ruv-neural system Status: Implementation checklist for v0.1 and v0.2 milestones


Table of Contents

  1. Crate Inventory
  2. Security Review
  3. Performance Review
  4. Action Items

Crate Inventory

CrateStatusLines (approx)Role
ruv-neural-coreImplemented~500Types, traits, error types, RVF format
ruv-neural-sensorImplemented~170Sensor data acquisition, calibration, quality
ruv-neural-signalImplemented~450Filtering, spectral analysis, Hilbert, connectivity
ruv-neural-graphStub~2Graph construction from signals
ruv-neural-mincutImplemented~700Stoer-Wagner, spectral cut, Cheeger, dynamic tracking
ruv-neural-embedImplemented~350Spectral, topology, node2vec embeddings
ruv-neural-memoryImplemented~425Embedding store, HNSW index
ruv-neural-decoderImplemented (lib)~25KNN, threshold, transition decoders
ruv-neural-esp32Implemented~265ADC interface, sensor readout
ruv-neural-wasmStub~2WebAssembly bindings
ruv-neural-vizImplemented (lib)~20Visualization, ASCII rendering, export
ruv-neural-cliStub~2CLI binary

Security Review

Input Validation

All public APIs must validate their inputs at system boundaries. This section catalogs each validation requirement and its current status.

Sensor Data Validation

CheckRequired InStatusNotes
sample_rate_hz > 0MultiChannelTimeSeries::newMISSINGConstructor accepts sample_rate_hz without validating it is positive and finite. Division by zero in duration_s() if zero.
num_channels > 0MultiChannelTimeSeries::newPASSReturns error if data.len() == 0.
Channel lengths equalMultiChannelTimeSeries::newPASSValidates all channels have the same length.
Non-NaN/Inf valuesAll signal processingMISSINGNo validation that input signals contain only finite f64 values. NaN propagation through FFT, PLV, and connectivity metrics produces silent garbage.
num_samples > 0AdcReader::read_samplesPASSReturns error if num_samples == 0.
Channel count > 0AdcReader::read_samplesPASSReturns error if no channels configured.
Channel index boundsAdcReader::load_bufferPASSReturns ChannelOutOfRange error.
sensitivity > 0SensorChannelMISSINGsensitivity_ft_sqrt_hz is a public field with no validation on construction.
sample_rate > 0SensorChannelMISSINGsample_rate_hz is a public field with no validation.

Recommendation: Add a SensorChannel::new() constructor that validates sensitivity_ft_sqrt_hz > 0, sample_rate_hz > 0, and that the orientation vector is a unit normal. Add sample_rate_hz > 0 and sample_rate_hz.is_finite() checks to MultiChannelTimeSeries::new. Add a validate_finite() utility for signal data.

Graph Construction Validation

CheckRequired InStatusNotes
Edge indices < num_nodesBrainGraph::adjacency_matrixPARTIALSilently skips out-of-bounds edges rather than reporting an error. This masks data corruption.
Edge weight is finiteBrainGraphMISSINGBrainEdge.weight is not validated. NaN/Inf weights propagate silently through Stoer-Wagner and spectral analysis.
num_nodes >= 2stoer_wagner_mincutPASSReturns proper error.
num_nodes >= 2fiedler_decompositionPASSReturns proper error.
num_nodes >= 2SpectralEmbedder::embedPASSReturns proper error.
num_nodes >= 2cheeger_constantPASSReturns proper error.
Self-loopsBrainGraphMISSINGNo validation that source != target on edges. Self-loops could inflate degree calculations.

Recommendation: Add a BrainGraph::validate() method that checks all edge indices are within bounds, weights are finite, and no self-loops exist. Call it from stoer_wagner_mincut, spectral_bisection, and SpectralEmbedder::embed. Consider making adjacency_matrix() return Result with an error for out-of-bounds edges instead of silently ignoring them.

RVF Format Validation

CheckRequired InStatusNotes
Magic bytesRvfHeader::validatePASSValidates against RVF_MAGIC.
VersionRvfHeader::validatePASSRejects unknown versions.
Header lengthRvfHeader::from_bytesPASSChecks bytes.len() < 22.
Data type tagRvfDataType::from_tagPASSReturns error for unknown tags.
metadata_json_len overflowRvfFile::read_fromCONCERNmetadata_json_len is cast from u32 to usize and used to allocate a Vec. A malicious file with metadata_json_len = u32::MAX (~4 GB) would cause an OOM allocation.
Payload lengthRvfFile::read_fromCONCERNread_to_end reads unbounded data into memory. A malicious file could exhaust memory.
JSON validityRvfFile::read_fromPASSUses serde_json::from_slice which returns an error on invalid JSON.
num_entries vs actual dataRvfFile::read_fromMISSINGThe header declares num_entries and embedding_dim, but these are never cross-checked against the actual payload size.

Recommendation: Add maximum size limits for metadata_json_len (e.g., 16 MB) and total payload size. Validate that num_entries * entry_size_for_type <= data.len() after reading. Use Read::take() to cap reads.

Embedding Validation

CheckRequired InStatusNotes
Non-empty vectorNeuralEmbedding::new (core)PASSReturns error for empty vectors.
Non-empty vectorNeuralEmbedding::new (embed)PASSReturns error for empty vectors.
Dimension matchcosine_similarity, euclidean_distancePASSReturns DimensionMismatch error.
Zero-norm handlingcosine_similarityPASSReturns 0.0 for zero-norm vectors.
NaN/Inf in vectorNeuralEmbedding::newMISSINGNo check for non-finite values in the embedding vector.

Memory Store Validation

CheckRequired InStatusNotes
Capacity > 0NeuralMemoryStore::newMISSINGCapacity 0 is accepted, producing a store that evicts on every insertion.
k > 0query_nearestMISSINGk=0 produces an empty result silently (acceptable but undocumented).
Dimension consistencyNeuralMemoryStore::storeMISSINGNo check that all stored embeddings have the same dimensionality. Mixed dimensions cause silent errors in query_nearest.

JSON Parsing

CheckStatusNotes
Uses serde derivePASSAll types use #[derive(Serialize, Deserialize)]. No manual parsing anywhere.
No unsafe JSON parsingPASSStandard serde_json throughout.

Memory Safety

CheckStatusNotes
No unsafe codePASSZero unsafe blocks across all crates.
Vec instead of raw pointersPASSAll data structures use Vec, HashMap, BinaryHeap.
ndarray for matrix opsNOT USEDDespite being listed in workspace.dependencies, matrix operations use Vec<Vec<f64>> throughout. This is bounds-checked but less efficient.
No C FFIPASSNo FFI calls. ESP32 code uses pure Rust types.
No std::mem::transmutePASSNone found.
No std::ptr usagePASSNone found.
Bounds checking on slicesPASSUses .get(), iterator methods, and Rust's built-in bounds checks.
Integer overflowCONCERNmax_raw_value() in adc.rs casts (1u32 << resolution_bits) - 1 to i16. If resolution_bits > 15, this overflows silently. Currently only 12 or 16 are intended, but 16 produces i16::MAX wrapping.

Recommendation: Add a validation check on resolution_bits in AdcConfig (must be <= 15 for i16 representation, or switch to u16/i32). Consider migrating Vec<Vec<f64>> matrix representations to ndarray::Array2<f64> for better cache performance and built-in bounds checking.


Data Privacy

Neural data is among the most sensitive personal data categories. This section covers data handling practices.

CheckStatusNotes
No PII in log messagesNEEDS AUDITThe crate uses tracing in workspace dependencies but currently has no tracing::info! or tracing::debug! calls with data fields. As logging is added, ensure neural data values, subject IDs, and session IDs are never logged at INFO level or below.
No neural data in error messagesPASSError messages contain structural information (dimensions, indices, version numbers) but not raw signal values or embeddings.
subject_id handlingCONCERNEmbeddingMetadata.subject_id is stored as plaintext Option<String>. This is PII that is included in serialized embeddings (serde), HNSW indices, and RVF files.
session_id handlingCONCERNSame concern as subject_id.
Memory store encryptionNOT IMPLEMENTEDNeuralMemoryStore holds embeddings in plaintext Vec<f64>. No encryption-at-rest.
Memory zeroization on dropNOT IMPLEMENTEDEmbedding data is not zeroed when dropped. Sensitive neural data persists in deallocated memory.
WASM data boundarySTUBWASM crate is not yet implemented. When implemented, must ensure no neural data is sent to external services without explicit user consent.
RVF file privacyCONCERNRvfFile serializes metadata as JSON, which may contain subject_id. No option to strip or anonymize metadata before export.

Recommendations:

  • Implement a Redactable trait for types that may contain PII, providing redact() and anonymize() methods.
  • Use the zeroize crate to zero sensitive data on drop for NeuralEmbedding, NeuralMemoryStore, and MultiChannelTimeSeries.
  • Add a strip_pii() method to RvfFile that removes or hashes identifiers before export.
  • Document privacy responsibilities in each crate's module documentation.
  • For v0.2: Add optional encryption-at-rest for NeuralMemoryStore using ring or aes-gcm.

Network Security (ESP32)

CheckStatusNotes
Node ID authenticationNOT IMPLEMENTEDESP32 crate (ruv-neural-esp32) is currently a local ADC reader with no network protocol. When TDM protocol is added, node IDs must be authenticated.
CRC32 integrityNOT IMPLEMENTEDNo data packet framing or integrity checks exist yet.
TLS encryptionNOT IMPLEMENTEDv0.1 has no network layer. Planned for v0.2.
Packet size limitsNOT IMPLEMENTEDNo packet protocol exists yet.
Buffer overflow preventionPARTIALAdcReader uses a fixed-size ring buffer (4096 samples), which prevents unbounded growth. However, load_buffer silently truncates data that exceeds buffer size rather than reporting it.
DMA configurationN/Adma_enabled is a configuration flag only; actual DMA is not implemented in std mode.

Recommendations for v0.2 TDM Protocol:

  • Authenticate node IDs using a pre-shared key or challenge-response.
  • Add CRC32 or CRC32-C to every data packet.
  • Set maximum packet size to 1460 bytes (single WiFi frame MTU).
  • Use DTLS or TLS 1.3 for encryption when available.
  • Rate-limit incoming packets per node to prevent flooding.
  • Validate all fields in received packets before processing.

Supply Chain

CheckStatusNotes
Minimal dependenciesPASSCore dependencies: thiserror, serde, serde_json, num-complex, rustfft, rand. All are well-maintained, widely-used crates.
No proc macros except serdePASSOnly serde's derive macros and thiserror's derive macro are used. clap's derive is CLI-only.
All deps from crates.ioPASSNo git dependencies or path dependencies outside the workspace.
Workspace-managed versionsPASSAll dependency versions are declared in [workspace.dependencies].
petgraph usageUNUSEDListed in workspace dependencies but not imported by any crate. Remove to reduce supply chain surface.
tokio usageUNUSEDListed in workspace dependencies but not imported by any crate. Remove unless async is planned.
ruvector-* cratesUNUSEDFive RuVector crates listed but not imported by any workspace member. Remove unused dependencies.
Cargo.lockPRESENTCargo.lock is committed, ensuring reproducible builds.

Recommendation: Run cargo deny check to audit for known vulnerabilities. Remove unused workspace dependencies (petgraph, tokio, ruvector-* crates) to minimize attack surface. Add cargo audit to CI.


Findings from Code Audit

SEC-001: RVF Unbounded Allocation (Severity: Medium)

Location: ruv-neural-core/src/rvf.rs, line 193

rust
let mut meta_bytes = vec![0u8; header.metadata_json_len as usize];

A crafted RVF file with metadata_json_len = 0xFFFFFFFF allocates 4 GB. Similarly, read_to_end on line 201 reads unbounded data.

Fix: Add maximum size constants and validate before allocating:

rust
const MAX_METADATA_LEN: u32 = 16 * 1024 * 1024; // 16 MB
const MAX_PAYLOAD_LEN: usize = 256 * 1024 * 1024; // 256 MB

if header.metadata_json_len > MAX_METADATA_LEN {
    return Err(RuvNeuralError::Serialization(
        format!("metadata_json_len {} exceeds maximum {}", header.metadata_json_len, MAX_METADATA_LEN)
    ));
}

SEC-002: Missing Sample Rate Validation (Severity: Medium)

Location: ruv-neural-core/src/signal.rs, MultiChannelTimeSeries::new

The sample_rate_hz parameter is not validated. A value of 0.0 causes division by zero in duration_s(). A negative or NaN value causes incorrect spectral analysis throughout the pipeline.

Fix: Add validation in the constructor:

rust
if sample_rate_hz <= 0.0 || !sample_rate_hz.is_finite() {
    return Err(RuvNeuralError::Signal(
        format!("sample_rate_hz must be positive and finite, got {}", sample_rate_hz)
    ));
}

SEC-003: NaN Propagation in Signal Processing (Severity: Low)

Location: ruv-neural-signal/src/connectivity.rs, all functions

If either input signal contains NaN, the Hilbert transform produces NaN outputs, which propagate silently through PLV, coherence, and all connectivity metrics. The result is a brain graph with NaN edge weights, which causes undefined behavior in Stoer-Wagner (infinite loops or wrong results).

Fix: Add a validate_signal helper and call it at entry points:

rust
fn validate_signal(signal: &[f64]) -> Result<()> {
    if signal.iter().any(|x| !x.is_finite()) {
        return Err(RuvNeuralError::Signal("Signal contains NaN or Inf values".into()));
    }
    Ok(())
}

SEC-004: Integer Overflow in ADC (Severity: Low)

Location: ruv-neural-esp32/src/adc.rs, AdcConfig::max_raw_value

rust
pub fn max_raw_value(&self) -> i16 {
    ((1u32 << self.resolution_bits) - 1) as i16
}

For resolution_bits = 16, this computes 65535 as i16 = -1, which causes incorrect voltage conversion (division by -1 flips sign).

Fix: Change return type to u16 or i32, or validate resolution_bits <= 15.

SEC-005: HNSW Visited Array Allocation (Severity: Low)

Location: ruv-neural-memory/src/hnsw.rs, search_layer, line 261

rust
let mut visited = vec![false; self.embeddings.len()];

This allocates a visited array proportional to the total number of embeddings on every search call. For large indices (100K+ embeddings), this causes unnecessary allocation pressure. More critically, if entry is >= self.embeddings.len(), the indexing on line 262 panics.

Fix: Use a HashSet<usize> instead of a boolean array for sparse visitation. Add bounds check on entry.


Performance Review

Computational Complexity

OperationComplexityTarget LatencyCurrent Status
FFT (1024 points)O(N log N)<1 msImplemented via rustfft (SIMD-optimized). Meets target.
Hilbert transformO(N log N)<1 msTwo FFTs (forward + inverse). Meets target for N <= 4096.
PLV (channel pair)O(N) + 2x FFT<0.5 msCalls hilbert_transform twice. Meets target for N <= 2048.
Coherence (channel pair)O(N) + 2x FFT<0.5 msSame as PLV.
Connectivity matrix (68 regions)O(N^2 x M)<10 msM = samples per channel, N = 68: 2,278 Hilbert pairs. May exceed target for long windows.
Stoer-Wagner mincut (68 nodes)O(V^3)<5 ms68^3 = ~314K operations. Meets target.
Spectral embedding (68 nodes)O(V^2 x k x iterations)<3 msWith k=8, iterations=100: 68^2 x 8 x 100 = ~37M ops. May be tight.
Fiedler decompositionO(V^2 x iterations)<2 ms1000 iterations x 68^2 = ~4.6M ops. Meets target.
Cheeger constant (exact, n<=16)O(2^n x n^2)<5 msExponential but capped at n=16: 65K x 256 = ~16M ops. Meets target.
HNSW insertO(log N x ef x M)<1 msef=200, M=16: ~3200 distance computations per insert. Meets target.
HNSW search (10K embeddings)O(log N x ef)<1 msef=50: ~50-200 distance computations. Meets target.
Brute-force NN (10K embeddings)O(N x d)<5 msd=256, N=10K: 2.56M f64 ops. Acceptable but HNSW preferred.
Full pipeline (68 regions)-<50 msSum of above stages. Should meet target.

Memory Usage

ComponentCalculationSize
64-channel x 1000 Hz x 8 bytes x 1s64 x 1000 x 8512 KB per second
Brain graph adjacency (68 nodes)68^2 x 8 bytes~37 KB
Brain graph adjacency (400 nodes)400^2 x 8 bytes~1.25 MB
Single embedding (256-d)256 x 8 bytes2 KB
Memory store (10K embeddings, 256-d)10K x 2 KB~20 MB
HNSW index (10K, M=16, 256-d)10K x (2KB + 16 x 16 bytes)~22.5 MB
Stoer-Wagner working memory (68 nodes)2 x 68^2 x 8 + 68 x vec overhead~75 KB
Spectral embedder (68 nodes, k=8)k x 68 x 8 + Laplacian 68^2 x 8~41 KB
RVF file in memoryheader + metadata + payloadVariable, unbounded (see SEC-001)

Optimization Opportunities

Immediate (v0.1)

  1. Eliminate redundant Hilbert transforms in connectivity matrix

    • compute_all_pairs calls hilbert_transform twice per channel pair.
    • For 68 channels, this means 68 x 67 = 4,556 Hilbert transforms instead of 68.
    • Fix: Pre-compute analytic signals for all channels, then compute metrics pairwise.
    • Expected speedup: ~67x for connectivity matrix computation.
  2. Replace Vec<Vec<f64>> with flat Vec<f64> for adjacency matrices

    • Current Vec<Vec<f64>> has poor cache locality due to heap-allocated inner Vecs.
    • Fix: Use Vec<f64> with manual row-major indexing, or migrate to ndarray::Array2<f64>.
    • Expected speedup: 2-4x for matrix-heavy operations (Stoer-Wagner, Laplacian).
  3. Avoid Vec::remove(0) in eviction

    • NeuralMemoryStore::evict_oldest calls self.embeddings.remove(0), which is O(n).
    • Fix: Use a VecDeque or circular buffer.
    • Expected speedup: O(1) eviction instead of O(n).
  4. Pre-allocate FFT planner

    • compute_psd, compute_stft, and hilbert_transform each create a new FftPlanner per call.
    • Fix: Cache the planner or use a thread-local planner.
    • Expected speedup: Eliminates repeated plan computation.

Medium-term (v0.2)

  1. Rayon for parallel channel processing

    • compute_all_pairs iterates channel pairs sequentially.
    • Fix: Use rayon::par_iter for the outer loop.
    • Expected speedup: Linear with core count for connectivity computation.
  2. SIMD for distance computations in HNSW

    • Euclidean distance in HnswIndex::distance uses scalar iteration.
    • Fix: Use packed_simd2 or auto-vectorization hints.
    • Expected speedup: 4-8x for 256-d vectors on AVX2.
  3. Sparse graph representation

    • Dense adjacency matrix wastes memory for sparse brain graphs.
    • For Schaefer400, storing all 160K entries when only ~10K edges exist is wasteful.
    • Fix: Use compressed sparse row (CSR) format or petgraph's sparse graph.
  4. Quantized embeddings for WASM

    • f64 embeddings are unnecessarily precise for browser-based applications.
    • Fix: Support f32 embeddings in WASM builds, halving memory and transfer size.

Long-term (v0.3+)

  1. Streaming signal processing

    • Current design loads entire time windows into memory.
    • Fix: Implement ring-buffer based streaming for real-time operation.
  2. GPU acceleration for large-scale spectral analysis

    • For Schaefer400 atlas, eigendecomposition of 400x400 matrices benefits from GPU.
    • Fix: Optional wgpu or vulkano backend for matrix operations.

ESP32 Constraints

ResourceLimitCurrent UsageStatus
SRAM520 KBRing buffer: 4096 x channels x 2 bytes = 8 KB (1 channel)OK
SRAM (multi-channel)520 KB4096 x 16 x 2 = 128 KB (16 channels)TIGHT
CPU240 MHz dual-coreADC sampling + data transmissionOK for 1 kHz
Flash4 MBBinary size with release profileNeeds measurement
WiFi throughput~1 Mbps sustained64 ch x 1000 Hz x 2 bytes = 128 KB/s = 1 MbpsAT LIMIT

Recommendations:

  • Use fixed-point arithmetic (i16 or Q15) instead of f64 on ESP32.
  • Implement delta encoding or simple compression for data packets.
  • Limit on-device processing to ADC readout and basic quality checks.
  • Move all signal processing (FFT, connectivity, graph construction) to the host.
  • Profile binary size with cargo bloat to ensure it fits in 4 MB flash.
  • Consider reducing ring buffer size for multi-channel configurations.

Benchmarking Recommendations

Per-Crate Microbenchmarks (criterion)

toml
# Add to each crate's Cargo.toml
[[bench]]
name = "benchmarks"
harness = false

[dev-dependencies]
criterion = { workspace = true }
CrateBenchmarkInput SizeMetric
ruv-neural-signalbench_hilbert_transform256, 512, 1024, 2048, 4096 samplesns/op
ruv-neural-signalbench_compute_psd1024, 4096 samplesns/op
ruv-neural-signalbench_plv_pair1024 samplesns/op
ruv-neural-signalbench_connectivity_matrix16, 32, 68 channels x 1024 samplesms/op
ruv-neural-mincutbench_stoer_wagner10, 20, 50, 68, 100 nodesus/op
ruv-neural-mincutbench_spectral_bisection10, 20, 50, 68, 100 nodesus/op
ruv-neural-mincutbench_cheeger_constant8, 12, 16 nodes (exact), 32, 68 (approx)us/op
ruv-neural-embedbench_spectral_embed20, 50, 68, 100 nodesus/op
ruv-neural-memorybench_brute_force_nn100, 1K, 10K embeddings x 256-dus/op
ruv-neural-memorybench_hnsw_insert1K, 10K embeddings x 256-dus/op
ruv-neural-memorybench_hnsw_search1K, 10K embeddings, k=10, ef=50us/op
ruv-neural-esp32bench_adc_read100, 1000 samples x 1-16 channelsus/op

Full Pipeline Profiling

bash
# Generate a flamegraph of the full pipeline
cargo flamegraph --bench full_pipeline -- --bench

# Memory profiling with DHAT
cargo test --features dhat-heap -- --test full_pipeline

WASM Performance

javascript
// When ruv-neural-wasm is implemented, measure with:
performance.mark('embed-start');
const embedding = ruv_neural.embed(graphData);
performance.mark('embed-end');
performance.measure('embed', 'embed-start', 'embed-end');

ESP32 Hardware Timing

rust
// Use esp-idf-hal's timer for hardware-level benchmarks
let start = esp_idf_hal::timer::now();
let samples = reader.read_samples(1000)?;
let elapsed_us = esp_idf_hal::timer::now() - start;

Performance Findings from Code Audit

PERF-001: Redundant Hilbert Transforms (Severity: High)

Location: ruv-neural-signal/src/connectivity.rs, compute_all_pairs

Each call to phase_locking_value, coherence, imaginary_coherence, or amplitude_envelope_correlation independently calls hilbert_transform on both input signals. In compute_all_pairs with 68 channels, each channel's analytic signal is computed 67 times.

Impact: For 68 channels x 1024 samples, this means 4,556 FFTs instead of 68. Estimated waste: ~98.5% of FFT compute in the connectivity matrix.

Fix: Pre-compute all analytic signals, then pass slices to pairwise metrics:

rust
pub fn compute_all_pairs_optimized(channels: &[Vec<f64>], metric: &ConnectivityMetric) -> Vec<Vec<f64>> {
    let analytics: Vec<Vec<Complex<f64>>> = channels.iter()
        .map(|ch| hilbert_transform(ch))
        .collect();
    // ... use pre-computed analytics for all pair computations
}

PERF-002: O(n) Eviction in Memory Store (Severity: Medium)

Location: ruv-neural-memory/src/store.rs, evict_oldest

rust
fn evict_oldest(&mut self) {
    self.embeddings.remove(0);  // O(n) shift
    self.rebuild_index();       // O(n) rebuild
}

For a store with 10K embeddings, every insertion at capacity triggers an O(n) shift and full index rebuild.

Fix: Use VecDeque<NeuralEmbedding> and maintain the index incrementally.

PERF-003: FFT Planner Re-creation (Severity: Medium)

Location: ruv-neural-signal/src/spectral.rs (lines 12-13), hilbert.rs (lines 25-27)

A new FftPlanner is created on every function call. rustfft caches FFT plans internally in the planner, but creating a new planner discards the cache.

Fix: Use a thread-local or static planner:

rust
thread_local! {
    static FFT_PLANNER: RefCell<FftPlanner<f64>> = RefCell::new(FftPlanner::new());
}

PERF-004: Dense Adjacency for Sparse Graphs (Severity: Low)

Location: ruv-neural-core/src/graph.rs, adjacency_matrix

Always allocates an N x N matrix even when the graph has far fewer edges. For Schaefer400 with ~5K edges, this allocates 1.25 MB for a matrix that is ~97% zeros.

Fix: Return a sparse representation for large graphs, or provide both adjacency_matrix() and sparse_adjacency().

PERF-005: Power Iteration Convergence Not Checked (Severity: Low)

Location: ruv-neural-mincut/src/spectral_cut.rs, largest_eigenvalue

Runs a fixed 200 iterations regardless of convergence. Many graphs converge in 20-50 iterations.

Fix: Add early termination when eigenvalue change < epsilon:

rust
if (eigenvalue - prev_eigenvalue).abs() < 1e-12 {
    break;
}

Note: fiedler_decomposition already has this check, but largest_eigenvalue does not.


Action Items

Critical (Must fix before v0.1 release)

  • SEC-001: Add maximum size limits to RVF deserialization
  • SEC-002: Validate sample_rate_hz > 0 and is_finite() in MultiChannelTimeSeries::new
  • SEC-004: Fix integer overflow in AdcConfig::max_raw_value
  • PERF-001: Pre-compute Hilbert transforms in compute_all_pairs

Important (Should fix before v0.1 release)

  • SEC-003: Add NaN/Inf validation for signal data at pipeline entry points
  • SEC-005: Add bounds check on HNSW entry point index
  • PERF-002: Replace Vec::remove(0) with VecDeque in memory store
  • PERF-003: Cache FFT planner across calls
  • Add BrainGraph::validate() for edge index bounds and weight finiteness
  • Add dimension consistency check to NeuralMemoryStore::store
  • Remove unused workspace dependencies (petgraph, tokio, ruvector-*)
  • Implement zeroize-on-drop for NeuralEmbedding and NeuralMemoryStore
  • Add strip_pii() to RvfFile
  • Migrate Vec<Vec<f64>> matrices to ndarray::Array2<f64>
  • Add Rayon parallelism for connectivity matrix computation
  • Add criterion benchmarks for all crates
  • Implement TDM protocol with CRC32 and node authentication
  • Add cargo deny and cargo audit to CI
  • Profile and optimize binary size for ESP32

Future (v0.3+)

  • Encryption-at-rest for NeuralMemoryStore
  • DTLS/TLS for ESP32 network protocol
  • Sparse graph representation for large atlases
  • f32 quantized embeddings for WASM
  • Streaming signal processing pipeline
  • GPU backend for large-scale spectral analysis

This document should be reviewed and updated after each milestone. All security findings should be verified as resolved before the corresponding release.