release: v0.7.1 — Security-Patch (R-01 bis R-06)
Sanctum Release / Build & Release (Windows x86_64) (push) Waiting to run

- R-01: Bereinigung verbliebener Restbehauptungen in Doku und Code (LEGAL.md, README.md, QUICKSTART.md, main.rs, crypto/storage/recovery/verify.rs)
- R-02: Lückenloser Carrier-Schutz in SanctumFs::copy (Quelle & Ziel) und sync (Pull-Skip & Push-Schutz)
- R-03: Shared Dateinamen-Validierung (validate_node_name) in pathutil.rs, durchgesetzt in storage.rs und vfs.rs
- R-04: Fail-closed Release-Packaging & obligatorische Minisign-Signatur in CI (.gitea/workflows/release.yaml) und Scripts
- R-05: Session-Token Beseitigung im argv: In-Process Win32 WNetAddConnection2W/WNetCancelConnection2W, kein gio argv-Token, Multi-Auth HTTP Middleware (Basic Auth, X-Sanctum-Token, Path-Fallback) mit 401 WWW-Authenticate
- R-06: Sofortiges Löschen von SANCTUM_RECOVERY_KEY aus der Prozessumgebung
This commit is contained in:
2026-09-18 19:49:38 +02:00
parent 436790abf0
commit 1cdb30147b
24 changed files with 663 additions and 174 deletions
+27 -7
View File
@@ -1094,10 +1094,8 @@ async fn test_webdav_loopback_session_token_and_host_validation() {
let fs = SanctumFs::new(db.clone(), dek.clone(), FORMAT_VERSION);
let session_token = "deadbeefcafebabe0123456789abcdef";
let token_path_prefix = format!("/{}", session_token);
let dav_server = DavHandler::builder()
.strip_prefix(token_path_prefix.clone())
.filesystem(Box::new(fs))
.locksystem(FakeLs::new())
.build_handler();
@@ -1109,11 +1107,11 @@ async fn test_webdav_loopback_session_token_and_host_validation() {
let server_handle = tokio::spawn(serve_webdav_loop(
listener,
dav_server,
token_path_prefix.clone(),
session_token.to_string(),
shutdown_rx,
));
// 3. Angriffstest A: Unbefugter Zugriff auf Root ohne Session-Token -> 403 Forbidden
// 3. Angriffstest A: Unbefugter Zugriff auf Root ohne Session-Token -> 401 Unauthorized mit WWW-Authenticate
{
let mut stream = TcpStream::connect(addr).await.unwrap();
let req = format!("GET / HTTP/1.1\r\nHost: 127.0.0.1:{}\r\nConnection: close\r\n\r\n", addr.port());
@@ -1122,8 +1120,13 @@ async fn test_webdav_loopback_session_token_and_host_validation() {
stream.read_to_end(&mut resp).await.unwrap();
let resp_str = String::from_utf8_lossy(&resp);
assert!(
resp_str.starts_with("HTTP/1.1 403 Forbidden"),
"Anfrage ohne Session-Token muss mit 403 Forbidden abgewiesen werden, erhalten:\n{}",
resp_str.starts_with("HTTP/1.1 401 Unauthorized"),
"Anfrage ohne Session-Token muss mit 401 Unauthorized abgewiesen werden, erhalten:\n{}",
resp_str
);
assert!(
resp_str.to_lowercase().contains("www-authenticate: basic realm=\"sanctum\""),
"401-Antwort muss WWW-Authenticate Header enthalten, erhalten:\n{}",
resp_str
);
}
@@ -1143,7 +1146,7 @@ async fn test_webdav_loopback_session_token_and_host_validation() {
);
}
// 5. Legitimer Zugriff: Korrektes Session-Token & Loopback-Host -> 200 OK
// 5. Legitimer Zugriff: Korrektes Session-Token via Path-Prefix Fallback -> 200 OK
{
let mut stream = TcpStream::connect(addr).await.unwrap();
let req = format!("OPTIONS /{}/ HTTP/1.1\r\nHost: 127.0.0.1:{}\r\nConnection: close\r\n\r\n", session_token, addr.port());
@@ -1162,6 +1165,23 @@ async fn test_webdav_loopback_session_token_and_host_validation() {
);
}
// 6. Legitimer Zugriff: HTTP Basic Auth (R-05) -> 200 OK
{
use base64::Engine;
let auth_hdr = format!("Basic {}", base64::engine::general_purpose::STANDARD.encode(format!("sanctum:{}", session_token)));
let mut stream = TcpStream::connect(addr).await.unwrap();
let req = format!("OPTIONS / HTTP/1.1\r\nHost: 127.0.0.1:{}\r\nAuthorization: {}\r\nConnection: close\r\n\r\n", addr.port(), auth_hdr);
stream.write_all(req.as_bytes()).await.unwrap();
let mut resp = Vec::new();
stream.read_to_end(&mut resp).await.unwrap();
let resp_str = String::from_utf8_lossy(&resp);
assert!(
resp_str.starts_with("HTTP/1.1 200 OK"),
"Legitime OPTIONS-Anfrage mit Basic Auth muss 200 OK liefern, erhalten:\n{}",
resp_str
);
}
// Server ordnungsgemäß beenden & aufräumen
let _ = shutdown_tx.send(true);
let _ = server_handle.await;