forked from bitcoinafterlife/bal-server
security: fix audit points 5-9 + optimize echo_push/info endpoints
- Point 5 (SSRF): Add URL validation for WELIST_SERVER_URL (src/validation.rs) - Point 6 (DB Access): Add DB path validation, symlink check, WAL mode (open_db) - Point 8 (HTTPS): Extract nginx config, add deployment checklist, bind warnings - Point 9 (Input Validation): Add NETWORKS check (404 for unknown), txid 64-hex validation - Optimize echo_push: parse transactions outside DB lock, batch duplicate check, N+1 xpub lookup eliminated via HashSet cache - Optimize echo_info: derive BIP32 address outside DB lock, minimize lock duration - Fix echo_stats SQL injection via parameter binding + add idx_stats_chain index - New regression tests: ssrf_tests, db_path_validation, input_validation_tests
This commit is contained in:
102
tests/db_path_validation.rs
Normal file
102
tests/db_path_validation.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use bal_server::db::open_db;
|
||||
use sqlite::State;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn test_open_db_blocks_traversal() {
|
||||
let res = open_db("../etc/passwd");
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"Path with '..' should be rejected"
|
||||
);
|
||||
let err = match res {
|
||||
Err(e) => e,
|
||||
Ok(_) => panic!("Expected error for traversal path"),
|
||||
};
|
||||
assert!(err.contains("'..'"), "Error should mention directory traversal: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_db_blocks_forbidden_absolute() {
|
||||
for path in ["/etc/passwd", "/proc/self/mem", "/dev/null", "/usr/bin/ls"] {
|
||||
let res = open_db(path);
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"Absolute path {} should be rejected", path
|
||||
);
|
||||
let err = match res {
|
||||
Err(e) => e,
|
||||
Ok(_) => panic!("Expected error for forbidden path {}", path),
|
||||
};
|
||||
assert!(
|
||||
err.contains("forbidden"),
|
||||
"Error should mention forbidden prefix: {}", err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_db_allows_relative() {
|
||||
let test_path = "tmp_test_bal.db";
|
||||
let _ = fs::remove_file(test_path);
|
||||
let res = open_db(test_path);
|
||||
assert!(
|
||||
res.is_ok(),
|
||||
"Valid relative path should be allowed"
|
||||
);
|
||||
let db = res.unwrap();
|
||||
drop(db);
|
||||
let _ = fs::remove_file(test_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_db_wal_pragmas_set() {
|
||||
let test_path = "tmp_test_wal.db";
|
||||
let _ = fs::remove_file(test_path);
|
||||
let _ = fs::remove_file(format!("{}-shm", test_path));
|
||||
let _ = fs::remove_file(format!("{}-wal", test_path));
|
||||
|
||||
let db = open_db(test_path).expect("Should open DB");
|
||||
let mut stmt = db.prepare("PRAGMA journal_mode;").unwrap();
|
||||
if let Ok(State::Row) = stmt.next() {
|
||||
let mode: String = stmt.read(0).unwrap();
|
||||
assert_eq!(
|
||||
mode, "wal",
|
||||
"SQLite journal mode should be WAL"
|
||||
);
|
||||
} else {
|
||||
panic!("Could not read journal_mode pragma");
|
||||
}
|
||||
|
||||
let _ = fs::remove_file(test_path);
|
||||
let _ = fs::remove_file(format!("{}-shm", test_path));
|
||||
let _ = fs::remove_file(format!("{}-wal", test_path));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_db_rejects_symlink() {
|
||||
let real = "tmp_test_real.db";
|
||||
let link = "tmp_test_link.db";
|
||||
let _ = fs::remove_file(real);
|
||||
let _ = fs::remove_file(link);
|
||||
fs::File::create(real).unwrap();
|
||||
fs::soft_link(real, link).unwrap();
|
||||
|
||||
let res = open_db(link);
|
||||
assert!(
|
||||
res.is_err(),
|
||||
"Symlink DB path should be rejected"
|
||||
);
|
||||
let err = match res {
|
||||
Err(e) => e,
|
||||
Ok(_) => panic!("Expected error for symlink"),
|
||||
};
|
||||
assert!(
|
||||
err.contains("symlink"),
|
||||
"Error should mention symlink: {}", err
|
||||
);
|
||||
|
||||
let _ = fs::remove_file(real);
|
||||
let _ = fs::remove_file(link);
|
||||
}
|
||||
Reference in New Issue
Block a user