release: v0.7.2 — Security Audit Remediation (SA-01 bis SA-07)
Sanctum Release / Build & Test (Windows x86_64) (push) Canceled after 0s
Sanctum Release / Sign & Release (push) Canceled after 0s

- SA-01: Container-DoS / KDF-Amplification Schutz mit Pre-KDF Validierung, max 2 Slots (nur 0 und 1), Slot 0 Pflicht und strikten BLOB-Laengen
- SA-02: Release-Signierung in CI entkoppelt (getrennte build und sign-and-release Jobs, Secret-Isolation)
- SA-03: Pinned Download-Integritaet fuer minisign.exe in CI via SHA-256
- SA-04: Immutable Action-Pinning (@sha) und Toolchain-Pinning (1.85.0) in CI
- SA-05: Session-Token vollstaendig aus URIs verbannt (403 Forbidden bei Vorkommen im Pfad/Query)
- SA-06: Constant-Time Token- und Auth-Vergleiche via subtle::ConstantTimeEq
- SA-07: Dokumentations-Klarstellung bzgl. logischem Shredding vs. physischer SSD/FTL/CoW-Persistenz
This commit is contained in:
2026-09-18 23:40:35 +02:00
parent 1cdb30147b
commit fba7f305e3
28 changed files with 3048 additions and 968 deletions
+48 -18
View File
@@ -22,13 +22,15 @@ fn create_test_container(path: &Path) -> (Database, [u8; 32]) {
let kek = derive_kek("sync_password", &salt, &kdf_params).unwrap();
let dek = generate_dek();
let (wrapped_dek, header_nonce, header_tag) = wrap_dek(&kek, &dek).unwrap();
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag).unwrap();
db.init_schema(&salt, &kdf_params, &wrapped_dek, &header_nonce, &header_tag)
.unwrap();
(db, *dek)
}
#[test]
fn test_sync_push_and_pull_basic() {
let temp_root = std::env::temp_dir().join(format!("sanctum_sync_test_{}", rand::random::<u64>()));
let temp_root =
std::env::temp_dir().join(format!("sanctum_sync_test_{}", rand::random::<u64>()));
let container_path = temp_root.join("test.sanctum");
let source_dir = temp_root.join("source");
let target_dir = temp_root.join("restored");
@@ -38,7 +40,11 @@ fn test_sync_push_and_pull_basic() {
// Testdateien anlegen
fs::write(source_dir.join("file1.txt"), b"Hello Sanctum Sync!").unwrap();
fs::write(source_dir.join("sub").join("file2.bin"), vec![0x42u8; 100_000]).unwrap();
fs::write(
source_dir.join("sub").join("file2.bin"),
vec![0x42u8; 100_000],
)
.unwrap();
let (db, dek) = create_test_container(&container_path);
@@ -55,7 +61,8 @@ fn test_sync_push_and_pull_basic() {
source_dir.to_str().unwrap(),
"/Backup",
&opts,
).expect("Sync push");
)
.expect("Sync push");
assert_eq!(stats.files_scanned, 2);
assert_eq!(stats.files_transferred, 2);
@@ -70,7 +77,8 @@ fn test_sync_push_and_pull_basic() {
source_dir.to_str().unwrap(),
"/Backup",
&opts,
).expect("Sync push delta");
)
.expect("Sync push delta");
assert_eq!(stats_delta.files_transferred, 0);
assert_eq!(stats_delta.files_skipped, 2);
@@ -88,13 +96,16 @@ fn test_sync_push_and_pull_basic() {
source_dir.to_str().unwrap(),
"/Backup",
&dry_opts,
).expect("Sync push dry-run");
)
.expect("Sync push dry-run");
assert_eq!(stats_dry.files_transferred, 1);
assert_eq!(stats_dry.files_skipped, 2);
// Verifizieren, dass new_file.txt im Tresor tatsächlich NICHT existiert
let node = db.resolve_path_in_vault("/Backup/new_file.txt", 0, &dek).unwrap();
let node = db
.resolve_path_in_vault("/Backup/new_file.txt", 0, &dek)
.unwrap();
assert!(node.is_none());
// 4. Pull
@@ -110,7 +121,8 @@ fn test_sync_push_and_pull_basic() {
"/Backup",
target_dir.to_str().unwrap(),
&pull_opts,
).expect("Sync pull");
)
.expect("Sync pull");
assert_eq!(stats_pull.files_transferred, 2);
@@ -126,7 +138,8 @@ fn test_sync_push_and_pull_basic() {
#[test]
fn test_sync_multi_megabyte_large_file() {
let temp_root = std::env::temp_dir().join(format!("sanctum_sync_large_{}", rand::random::<u64>()));
let temp_root =
std::env::temp_dir().join(format!("sanctum_sync_large_{}", rand::random::<u64>()));
let container_path = temp_root.join("test_large.sanctum");
let source_dir = temp_root.join("source");
let target_dir = temp_root.join("restored");
@@ -159,10 +172,14 @@ fn test_sync_multi_megabyte_large_file() {
source_dir.to_str().unwrap(),
"/LargeTest",
&opts,
).expect("Sync push large");
)
.expect("Sync push large");
assert_eq!(stats.files_transferred, 1);
assert_eq!(stats.bytes_transferred, 3 * (CHUNK_SIZE as u64) + 512 * 1024);
assert_eq!(
stats.bytes_transferred,
3 * (CHUNK_SIZE as u64) + 512 * 1024
);
// Pull
let mut pull_opts = SyncOptions::default();
@@ -177,7 +194,8 @@ fn test_sync_multi_megabyte_large_file() {
"/LargeTest",
target_dir.to_str().unwrap(),
&pull_opts,
).expect("Sync pull large");
)
.expect("Sync pull large");
let restored = fs::read(target_dir.join("large_payload.bin")).unwrap();
let original = fs::read(&large_file_path).unwrap();
@@ -189,7 +207,8 @@ fn test_sync_multi_megabyte_large_file() {
#[test]
fn test_sync_delete_and_exclude_flags() {
let temp_root = std::env::temp_dir().join(format!("sanctum_sync_flags_{}", rand::random::<u64>()));
let temp_root =
std::env::temp_dir().join(format!("sanctum_sync_flags_{}", rand::random::<u64>()));
let container_path = temp_root.join("test_flags.sanctum");
let source_dir = temp_root.join("source");
@@ -214,10 +233,14 @@ fn test_sync_delete_and_exclude_flags() {
source_dir.to_str().unwrap(),
"/Files",
&opts,
).unwrap();
)
.unwrap();
assert_eq!(stats.files_transferred, 2); // keep.txt und remove_me.txt
assert!(db.resolve_path_in_vault("/Files/ignore.tmp", 0, &dek).unwrap().is_none());
assert!(db
.resolve_path_in_vault("/Files/ignore.tmp", 0, &dek)
.unwrap()
.is_none());
// 2. Lokale Datei löschen und Sync mit --delete ausführen
fs::remove_file(source_dir.join("remove_me.txt")).unwrap();
@@ -231,11 +254,18 @@ fn test_sync_delete_and_exclude_flags() {
source_dir.to_str().unwrap(),
"/Files",
&opts,
).unwrap();
)
.unwrap();
assert_eq!(stats_del.files_deleted, 1);
assert!(db.resolve_path_in_vault("/Files/remove_me.txt", 0, &dek).unwrap().is_none());
assert!(db.resolve_path_in_vault("/Files/keep.txt", 0, &dek).unwrap().is_some());
assert!(db
.resolve_path_in_vault("/Files/remove_me.txt", 0, &dek)
.unwrap()
.is_none());
assert!(db
.resolve_path_in_vault("/Files/keep.txt", 0, &dek)
.unwrap()
.is_some());
let _ = fs::remove_dir_all(&temp_root);
}