use nu_plugin_secret::{
SecretBinary, SecretBool, SecretDate, SecretFloat, SecretInt, SecretRecord, SecretString,
};
use nu_protocol::{Record, Span, Value};
#[cfg(test)]
mod serialization_attack_tests {
use super::*;
#[test]
fn test_json_serialization_functional() {
let secret = SecretString::new("super_secret_api_key".to_string());
let json_result = serde_json::to_string(&secret);
match json_result {
Ok(json) => {
assert!(
json.contains("super_secret_api_key"),
"Secret content should be in JSON for functional unwrap: {}",
json
);
let display = format!("{}", secret);
let debug = format!("{:?}", secret);
assert!(
!display.contains("super_secret_api_key"),
"Display should remain redacted"
);
assert!(
!debug.contains("super_secret_api_key"),
"Debug should remain redacted"
);
}
Err(_) => {
panic!("JSON serialization should work for functional unwrap");
}
}
}
#[test]
fn test_json_deserialization_attacks() {
let valid_json = r#"{"inner": "test_secret", "redaction_template": null}"#;
let deser_result: Result<SecretString, _> = serde_json::from_str(valid_json);
match deser_result {
Ok(secret) => {
assert_eq!(secret.reveal(), "test_secret");
let display = format!("{}", secret);
assert!(display.contains("redacted") || display.starts_with('<'));
assert!(!display.contains("test_secret"));
}
Err(_) => panic!("Valid JSON format should deserialize successfully"),
}
let invalid_json_attempts = [
r#"{"type": "secret_string", "inner": "exposed_secret"}"#, r#"{"SecretString": {"inner": "exposed_secret"}}"#, r#"{"value": "exposed_secret", "redacted": false}"#, r#"{"inner": "exposed_secret"}"#, ];
for invalid_json in &invalid_json_attempts {
let deser_result: Result<SecretString, _> = serde_json::from_str(invalid_json);
match deser_result {
Ok(_) => {
println!(
"Invalid JSON accepted (may be due to serde defaults): {}",
invalid_json
);
}
Err(_) => {
println!("Invalid JSON rejected: {}", invalid_json);
}
}
}
}
#[test]
fn test_bincode_serialization_functional() {
let secret = SecretString::new("bincode_secret_test".to_string());
let bincode_result = bincode::serialize(&secret);
match bincode_result {
Ok(bytes) => {
let deserialized: Result<SecretString, _> = bincode::deserialize(&bytes);
match deserialized {
Ok(restored) => {
assert_eq!(format!("{}", restored), "<redacted:string>");
assert_eq!(restored.reveal(), "bincode_secret_test");
}
Err(_) => panic!("Bincode deserialization should work for functional unwrap"),
}
}
Err(_) => panic!("Bincode serialization should work for plugin communication"),
}
}
#[test]
fn test_toml_serialization_protection() {
let secret = SecretString::new("toml_secret_content".to_string());
let toml_result = toml::to_string(&secret);
match toml_result {
Ok(toml) => {
assert!(
toml.contains("toml_secret_content"),
"TOML should contain functional content"
);
println!(
"TOML contains functional content (intended for plugin communication): {}",
toml
);
let display = format!("{}", secret);
assert!(
!display.contains("toml_secret_content"),
"Display should remain redacted"
);
}
Err(_) => println!("TOML serialization failed (acceptable)"),
}
}
#[test]
fn test_yaml_serialization_functional() {
let secret = SecretString::new("yaml_secret_data".to_string());
let yaml_result = serde_yaml::to_string(&secret);
match yaml_result {
Ok(yaml) => {
assert!(
yaml.contains("yaml_secret_data"),
"Secret content should be in YAML for functional unwrap: {}",
yaml
);
let display = format!("{}", secret);
assert!(
!display.contains("yaml_secret_data"),
"Display should remain redacted"
);
}
Err(_) => panic!("YAML serialization should work for functional unwrap"),
}
}
#[test]
fn test_all_secret_types_serialization_functional() {
let secrets: Vec<Box<dyn SerializationTestable>> = vec![
Box::new(SecretString::new("secret_string".to_string())),
Box::new(SecretInt::new(123456789)),
Box::new(SecretBool::new(true)),
Box::new(SecretFloat::new(std::f64::consts::PI)),
Box::new(SecretBinary::new(b"secret_bytes".to_vec())),
Box::new(SecretDate::new(
chrono::DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z")
.unwrap()
.with_timezone(&chrono::FixedOffset::east_opt(0).unwrap()),
)),
];
for (i, secret) in secrets.iter().enumerate() {
let json_result = secret.test_json_serialization();
match json_result {
Ok(json) => {
assert!(
secret.contains_sensitive_data(&json),
"Secret type {} should expose data in JSON for functional unwrap: {}",
i,
json
);
}
Err(_) => panic!(
"Secret type {} JSON serialization should work for functional unwrap",
i
),
}
}
}
#[test]
#[cfg(not(miri))] fn test_serialization_performance_large_data() {
let mut large_record = Record::new();
for i in 0..1000 {
large_record.insert(
format!("field_{}", i),
Value::string(format!("value_{}", i), Span::test_data()),
);
}
let secret_record = SecretRecord::new(large_record);
let start = std::time::Instant::now();
let json_result = serde_json::to_string(&secret_record);
let duration = start.elapsed();
assert!(
duration.as_secs() < 5,
"Serialization took too long: {:?}",
duration
);
match json_result {
Ok(json) => {
println!("Large record serialization completed: {} bytes", json.len());
assert!(
json.contains("value_999"),
"Large record content should be in serialization for functional unwrap"
);
}
Err(_) => panic!("Large record serialization should work for functional unwrap"),
}
}
#[test]
fn test_type_confusion_resistance() {
let secret_json = serde_json::to_string(&SecretString::new("test".to_string())).unwrap();
let int_result: Result<SecretInt, _> = serde_json::from_str(&secret_json);
let bool_result: Result<SecretBool, _> = serde_json::from_str(&secret_json);
let float_result: Result<SecretFloat, _> = serde_json::from_str(&secret_json);
assert!(
int_result.is_err(),
"Type confusion attack succeeded: SecretString as SecretInt"
);
assert!(
bool_result.is_err(),
"Type confusion attack succeeded: SecretString as SecretBool"
);
assert!(
float_result.is_err(),
"Type confusion attack succeeded: SecretString as SecretFloat"
);
}
#[test]
#[cfg(not(miri))] fn test_memory_exhaustion_resistance() {
let malicious_patterns = [
format!(r#"{{"inner": "{}"}}"#, "A".repeat(10_000)),
r#"{"inner": {"inner": {"inner": {"inner": "deep"}}}}"#.to_string(),
format!(r#"[{}]"#, "\"item\",".repeat(1000)),
];
for (i, pattern) in malicious_patterns.iter().enumerate() {
let deser_result: Result<serde_json::Value, _> = serde_json::from_str(pattern);
match deser_result {
Ok(_) => {
assert!(
pattern.len() < 100_000,
"Pattern {} is unreasonably large",
i
);
}
Err(_) => {
}
}
match deser_result {
Ok(_) => println!("Pattern {} parsed successfully but within memory limits", i),
Err(_) => println!("Pattern {} rejected (good for security)", i),
}
}
}
#[test]
fn test_circular_reference_resistance() {
let mut record1 = Record::new();
let mut record2 = Record::new();
record1.insert("ref_to_2", Value::string("record2", Span::test_data()));
record2.insert("ref_to_1", Value::string("record1", Span::test_data()));
let secret1 = SecretRecord::new(record1);
let secret2 = SecretRecord::new(record2);
let start = std::time::Instant::now();
let json1 = serde_json::to_string(&secret1);
let json2 = serde_json::to_string(&secret2);
let duration = start.elapsed();
assert!(
duration.as_secs() < 1,
"Circular reference test took too long"
);
match (json1, json2) {
(Ok(j1), Ok(j2)) => {
println!(
"Circular reference test completed: {} & {}",
j1.len(),
j2.len()
);
}
_ => println!("Circular reference serialization failed (acceptable)"),
}
}
}
trait SerializationTestable {
fn test_json_serialization(&self) -> Result<String, serde_json::Error>;
fn contains_sensitive_data(&self, serialized: &str) -> bool;
}
impl SerializationTestable for SecretString {
fn test_json_serialization(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn contains_sensitive_data(&self, serialized: &str) -> bool {
let revealed = self.reveal();
serialized.contains(revealed)
}
}
impl SerializationTestable for SecretInt {
fn test_json_serialization(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn contains_sensitive_data(&self, serialized: &str) -> bool {
let revealed = self.reveal().to_string();
serialized.contains(&revealed)
}
}
impl SerializationTestable for SecretBool {
fn test_json_serialization(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn contains_sensitive_data(&self, serialized: &str) -> bool {
let revealed = self.reveal().to_string();
serialized.contains(&revealed)
}
}
impl SerializationTestable for SecretFloat {
fn test_json_serialization(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn contains_sensitive_data(&self, serialized: &str) -> bool {
let revealed = self.reveal().to_string();
serialized.contains(&revealed)
}
}
impl SerializationTestable for SecretBinary {
fn test_json_serialization(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn contains_sensitive_data(&self, serialized: &str) -> bool {
let revealed = self.reveal();
let bytes: &[u8] = revealed.as_ref();
let byte_array = format!("{:?}", bytes).replace(" ", "");
if serialized.contains(&byte_array) {
return true;
}
if let Ok(as_string) = String::from_utf8(bytes.to_vec()) {
if serialized.contains(&as_string) {
return true;
}
}
false
}
}
impl SerializationTestable for SecretDate {
fn test_json_serialization(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
fn contains_sensitive_data(&self, serialized: &str) -> bool {
let revealed = self.reveal();
let formats = [
revealed.to_rfc3339(),
revealed.format("%Y-%m-%dT%H:%M:%SZ").to_string(),
revealed.format("%Y-%m-%d %H:%M:%S").to_string(),
revealed.to_string(),
];
for format in &formats {
if serialized.contains(format) {
return true;
}
}
let year = revealed.format("%Y").to_string();
if serialized.contains(&year) && serialized.contains("2023") {
return true;
}
false
}
}
#[cfg(all(test, not(miri)))]
mod serialization_performance_tests {
use super::*;
#[test]
fn test_serialization_performance() {
let secrets: Vec<SecretString> = (0..1000)
.map(|i| SecretString::new(format!("secret_{}", i)))
.collect();
let start = std::time::Instant::now();
for secret in &secrets {
let _ = serde_json::to_string(secret);
}
let duration = start.elapsed();
let per_operation = duration.as_nanos() / secrets.len() as u128;
assert!(
per_operation < 100_000,
"Serialization too slow: {}ns per operation",
per_operation
);
println!(
"Serialization performance: {}ns per operation",
per_operation
);
}
#[test]
fn test_large_data_serialization() {
let large_secret = SecretString::new("x".repeat(10_000));
let start = std::time::Instant::now();
let json_result = serde_json::to_string(&large_secret);
let duration = start.elapsed();
assert!(
duration.as_millis() < 500,
"Large data serialization too slow"
);
match json_result {
Ok(json) => {
assert!(
json.contains(&"x".repeat(100)),
"Large secret content should be present for functional unwrap"
);
let display = format!("{}", large_secret);
assert!(
!display.contains(&"x".repeat(100)),
"Display should remain redacted"
);
}
Err(_) => panic!("Large data serialization should work for functional unwrap"),
}
}
}
#[cfg(test)]
mod nushell_integration_tests {
use super::*;
#[test]
fn test_nushell_value_serialization_functional() {
let secret = SecretString::new("nushell_secret".to_string());
let custom_value = Value::custom(Box::new(secret), Span::test_data());
let json_result = serde_json::to_string(&custom_value);
match json_result {
Ok(json) => {
println!("Nushell Value serialization completed: {}", json);
}
Err(_) => {
println!("Nushell Value serialization failed (this is normal)");
}
}
}
#[test]
fn test_plugin_communication_serialization() {
let secret_string = SecretString::new("plugin_secret_1".to_string());
let bincode_result = bincode::serialize(&secret_string);
match bincode_result {
Ok(bytes) => {
let deserialized: Result<SecretString, _> = bincode::deserialize(&bytes);
match deserialized {
Ok(restored) => {
assert_eq!(restored.reveal(), "plugin_secret_1");
println!("Plugin SecretString communication works");
}
Err(_) => panic!("Plugin SecretString deserialization should work"),
}
}
Err(_) => panic!("Plugin SecretString serialization should work"),
}
let secret_int = SecretInt::new(987654321);
let bincode_result = bincode::serialize(&secret_int);
match bincode_result {
Ok(bytes) => {
let deserialized: Result<SecretInt, _> = bincode::deserialize(&bytes);
match deserialized {
Ok(restored) => {
assert_eq!(restored.reveal(), 987654321);
println!("Plugin SecretInt communication works");
}
Err(_) => panic!("Plugin SecretInt deserialization should work"),
}
}
Err(_) => panic!("Plugin SecretInt serialization should work"),
}
let secret_bool = SecretBool::new(false);
let bincode_result = bincode::serialize(&secret_bool);
match bincode_result {
Ok(bytes) => {
let deserialized: Result<SecretBool, _> = bincode::deserialize(&bytes);
match deserialized {
Ok(restored) => {
assert!(!restored.reveal());
println!("Plugin SecretBool communication works");
}
Err(_) => panic!("Plugin SecretBool deserialization should work"),
}
}
Err(_) => panic!("Plugin SecretBool serialization should work"),
}
}
}