From 280b3d25ec0898c2c792b842a02d957f2da1565b Mon Sep 17 00:00:00 2001 From: serversdown Date: Thu, 2 Jul 2026 06:45:17 +0000 Subject: [PATCH] fix(db): WAL-safe safety backup + tests for validate-first and zip-traversal guard --- sfm/db_snapshot.py | 2 +- tests/test_db_snapshot.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/sfm/db_snapshot.py b/sfm/db_snapshot.py index 98cbdf7..84531ab 100644 --- a/sfm/db_snapshot.py +++ b/sfm/db_snapshot.py @@ -78,7 +78,7 @@ def restore_db_and_waveforms(db_path: Path, waveform_root: Path, if db_path.exists(): ts = datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S") safety = db_path.with_name(f"{db_path.name}.pre-restore-{ts}") - shutil.copy2(db_path, safety) + snapshot_db(db_path, safety) safety_name = safety.name # Clear the OUTGOING db's WAL sidecars so they cannot attach to the incoming file. diff --git a/tests/test_db_snapshot.py b/tests/test_db_snapshot.py index d91e45f..9101353 100644 --- a/tests/test_db_snapshot.py +++ b/tests/test_db_snapshot.py @@ -129,5 +129,35 @@ def test_restore_rejects_non_sqlite(tmp_path): bogus.write_bytes(b"not a sqlite file at all") wf_root = tmp_path / "waveforms" wf_root.mkdir() + original_bytes = db_path.read_bytes() with pytest.raises(ValueError): restore_db_and_waveforms(db_path, wf_root, bogus, None) + assert db_path.read_bytes() == original_bytes + assert list(tmp_path.glob("*.pre-restore-*")) == [] + + +def test_restore_skips_unsafe_zip_members(tmp_path): + import zipfile + from sfm.db_snapshot import restore_db_and_waveforms + + db_path = tmp_path / "seismo_relay.db" + _make_db(db_path, rows=1) + + new_db = tmp_path / "incoming.db" + _make_db(new_db, rows=2) + + wf_root = tmp_path / "waveforms" + wf_root.mkdir() + wf_zip = tmp_path / "wf.zip" + with zipfile.ZipFile(wf_zip, "w") as zf: + zf.writestr("BE111/ok.h5", b"good") + zf.writestr("../evil.txt", b"escape via parent dir") + zf.writestr("/tmp/abs_evil.txt", b"escape via absolute path") + zf.writestr("baddir/", b"") + + result = restore_db_and_waveforms(db_path, wf_root, new_db, wf_zip) + + assert (wf_root / "BE111" / "ok.h5").exists() + assert result["waveforms_unpacked"] == 1 + assert not (wf_root.parent / "evil.txt").exists() + assert not Path("/tmp/abs_evil.txt").exists()