Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
191
vendor/ruvector/examples/dna/tests/security_tests.rs
vendored
Normal file
191
vendor/ruvector/examples/dna/tests/security_tests.rs
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
//! Security validation tests for DNA analyzer - NO MOCKS, real computation only
|
||||
use ::rvdna::error::DnaError;
|
||||
use ::rvdna::types::*;
|
||||
use ::rvdna::VectorEntry;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn test_buffer_overflow_protection() {
|
||||
// 10M+ bases shouldn't cause OOM/crash
|
||||
let large_size = 10_000_000;
|
||||
let bases: Vec<Nucleotide> = (0..large_size)
|
||||
.map(|i| match i % 4 {
|
||||
0 => Nucleotide::A,
|
||||
1 => Nucleotide::C,
|
||||
2 => Nucleotide::G,
|
||||
_ => Nucleotide::T,
|
||||
})
|
||||
.collect();
|
||||
let seq = DnaSequence::new(bases);
|
||||
assert_eq!(seq.len(), large_size);
|
||||
let rc = seq.reverse_complement();
|
||||
assert_eq!(rc.len(), large_size);
|
||||
assert!(seq.to_kmer_vector(11, 512).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_base_handling() {
|
||||
// Non-ACGTN characters rejected gracefully
|
||||
for input in ["ACGTX", "ACGT123", "ACGT!@#"] {
|
||||
let result = DnaSequence::from_str(input);
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result.unwrap_err(), DnaError::InvalidSequence(_)));
|
||||
}
|
||||
assert!(DnaSequence::from_str("ACGTN").is_ok());
|
||||
assert!(DnaSequence::from_str("acgtn").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unicode_injection() {
|
||||
// Unicode/malicious IDs don't break indexing
|
||||
let seq = DnaSequence::from_str("ACGTACGT").unwrap();
|
||||
let vector = seq.to_kmer_vector(3, 128).unwrap();
|
||||
let temp_dir = std::env::temp_dir().join(format!("dna_test_{}", std::process::id()));
|
||||
let _ = std::fs::create_dir_all(&temp_dir);
|
||||
let index = KmerIndex::new(3, 128, temp_dir.join("unicode").to_str().unwrap()).unwrap();
|
||||
|
||||
for id in ["seq_cafe_dna", "patient123", "seq_hidden"] {
|
||||
let entry = VectorEntry {
|
||||
id: Some(id.to_string()),
|
||||
vector: vector.clone(),
|
||||
metadata: None,
|
||||
};
|
||||
assert!(index.db().insert(entry).is_ok());
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_traversal_prevention() {
|
||||
// Verify KmerIndex handles unusual paths without panicking
|
||||
// The key security property: operations complete or fail gracefully
|
||||
let temp_dir = std::env::temp_dir().join(format!("dna_path_{}", std::process::id()));
|
||||
let _ = std::fs::create_dir_all(&temp_dir);
|
||||
|
||||
for path in ["../../../tmp/evil", "../../etc/passwd"] {
|
||||
let full_path = temp_dir.join(path);
|
||||
// KmerIndex creation with traversal paths should either succeed
|
||||
// (contained to actual resolved path) or fail gracefully - never panic
|
||||
let result =
|
||||
std::panic::catch_unwind(|| KmerIndex::new(3, 128, full_path.to_str().unwrap()));
|
||||
assert!(result.is_ok(), "Path traversal should not cause panic");
|
||||
}
|
||||
|
||||
// Clean up any created dirs
|
||||
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||
let _ = std::fs::remove_dir_all(std::env::temp_dir().join("evil"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_overflow_kmer() {
|
||||
// k=64 would overflow, k=0 invalid
|
||||
let seq = DnaSequence::from_str("ACGTACGTACGTACGT").unwrap();
|
||||
assert!(matches!(
|
||||
seq.to_kmer_vector(64, 512).unwrap_err(),
|
||||
DnaError::InvalidKmerSize(64)
|
||||
));
|
||||
assert!(seq.to_kmer_vector(0, 512).is_err());
|
||||
assert!(seq.to_kmer_vector(11, 512).is_ok());
|
||||
assert!(seq.to_kmer_vector(15, 512).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_input_safety() {
|
||||
// Empty inputs handled safely
|
||||
assert!(matches!(
|
||||
DnaSequence::from_str("").unwrap_err(),
|
||||
DnaError::EmptySequence
|
||||
));
|
||||
let empty = DnaSequence::new(vec![]);
|
||||
assert!(empty.is_empty() && empty.len() == 0);
|
||||
assert!(empty.complement().is_empty());
|
||||
assert!(empty.reverse_complement().is_empty());
|
||||
assert_eq!(empty.to_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_byte_handling() {
|
||||
// Null bytes rejected
|
||||
assert!(DnaSequence::from_str("ACGT\0").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_concurrent_access_safety() {
|
||||
// 10 threads accessing VectorDB concurrently
|
||||
let temp_dir = std::env::temp_dir().join(format!("dna_conc_{}", std::process::id()));
|
||||
let _ = std::fs::create_dir_all(&temp_dir);
|
||||
let index = Arc::new(Mutex::new(
|
||||
KmerIndex::new(3, 128, temp_dir.join("idx").to_str().unwrap()).unwrap(),
|
||||
));
|
||||
|
||||
let handles: Vec<_> = (0..10)
|
||||
.map(|i| {
|
||||
let idx_clone = Arc::clone(&index);
|
||||
thread::spawn(move || {
|
||||
let seq = DnaSequence::from_str("ACGTACGTACGT").unwrap();
|
||||
let entry = VectorEntry {
|
||||
id: Some(format!("seq_{}", i)),
|
||||
vector: seq.to_kmer_vector(3, 128).unwrap(),
|
||||
metadata: None,
|
||||
};
|
||||
idx_clone.lock().unwrap().db().insert(entry).unwrap();
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
for h in handles {
|
||||
assert!(h.join().is_ok());
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&temp_dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quality_score_bounds() {
|
||||
// Phred >93 rejected, 0-93 accepted
|
||||
assert!(matches!(
|
||||
QualityScore::new(100).unwrap_err(),
|
||||
DnaError::InvalidQuality(100)
|
||||
));
|
||||
assert!(QualityScore::new(0).is_ok());
|
||||
assert!(QualityScore::new(93).is_ok());
|
||||
assert!((QualityScore::new(30).unwrap().to_error_probability() - 0.001).abs() < 1e-6);
|
||||
assert!((QualityScore::new(0).unwrap().to_error_probability() - 1.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variant_position_overflow() {
|
||||
// u64::MAX positions handled
|
||||
let pos = GenomicPosition {
|
||||
chromosome: 25,
|
||||
position: u64::MAX,
|
||||
reference_allele: Nucleotide::A,
|
||||
alternate_allele: Some(Nucleotide::G),
|
||||
};
|
||||
assert_eq!(pos.position, u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_methylation_bounds() {
|
||||
// Beta values clamped to [0,1]
|
||||
for val in [-0.5f32, 0.0, 0.5, 1.0, 1.5] {
|
||||
let clamped = val.clamp(0.0, 1.0);
|
||||
assert!(clamped >= 0.0 && clamped <= 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deterministic_output() {
|
||||
// Same input -> same output (no randomness)
|
||||
let seq = DnaSequence::from_str("ACGTACGTACGTACGT").unwrap();
|
||||
assert_eq!(
|
||||
seq.to_kmer_vector(11, 512).unwrap(),
|
||||
seq.to_kmer_vector(11, 512).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
seq.reverse_complement().to_string(),
|
||||
seq.reverse_complement().to_string()
|
||||
);
|
||||
assert_eq!(seq.complement().to_string(), seq.complement().to_string());
|
||||
assert_eq!(seq.to_string(), seq.to_string());
|
||||
}
|
||||
Reference in New Issue
Block a user