Refuse a relative index_path at config load (closes #430)
check / check (push) Successful in 11m12s

A relative index_path resolved against the working directory, so a
backup run from another directory started from an empty index. Validate
now refuses it, naming the value, with the same wording as the file://
storage_url check. ~ is still expanded first. The check sees the final
value, so a relative VAULTIK_INDEX_PATH is refused too; an explicitly
empty index_path is refused as well.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 04:23:56 +00:00
1 parent d8d13d1c8b
commit 75a9ef0889
4 files changed
+73 -1

No files matched your search

+61
View File
@@ -165,6 +165,7 @@ func TestValidateBlobSizeLimit(t *testing.T) {
AgeRecipients: []string{testSneakAgePublicKey},
Snapshots: map[string]SnapshotConfig{"test": {Paths: []string{"/tmp/src"}}},
StorageURL: "file:///tmp/vaultik-test-store",
IndexPath: "/tmp/vaultik-test-index.sqlite",
ChunkSize: chunkSize,
BlobSizeLimit: blobLimit,
CompressionLevel: 3,
@@ -510,6 +511,65 @@ func TestLoadRejectsRelativeFileURL(t *testing.T) {
}
}
// TestLoadIndexPath checks that a relative index_path fails at load, naming
// the value. Accepted, it would resolve against the working directory, so a
// backup run from another directory would start from an empty index. A path
// starting with ~ is expanded and loads.
func TestLoadIndexPath(t *testing.T) {
t.Parallel()
home, err := os.UserHomeDir()
if err != nil {
t.Fatalf("home directory: %v", err)
}
load := func(t *testing.T, indexPath string) (*Config, error) {
t.Helper()
data := "snapshots:\n" +
" test:\n" +
" paths: [/tmp/vaultik-test-source]\n" +
"storage_url: file:///tmp/vaultik-test-storage\n" +
"index_path: " + indexPath + "\n"
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(data), 0o600)
if err != nil {
t.Fatalf("write config: %v", err)
}
return Load(path)
}
t.Run("relative path is rejected", func(t *testing.T) {
t.Parallel()
_, err := load(t, "relative/index.sqlite")
if !errors.Is(err, errIndexPathNotAbsolute) {
t.Fatalf("Load() error = %v, want errIndexPathNotAbsolute", err)
}
if !strings.Contains(err.Error(), `"relative/index.sqlite"`) {
t.Errorf("error %q does not name the value", err.Error())
}
})
t.Run("path starting with ~ is expanded", func(t *testing.T) {
t.Parallel()
cfg, err := load(t, "~/vaultik/index.sqlite")
if err != nil {
t.Fatalf("Load() unexpected error: %v", err)
}
want := filepath.Join(home, "vaultik", "index.sqlite")
if cfg.IndexPath != want {
t.Errorf("IndexPath = %q, want %q", cfg.IndexPath, want)
}
})
}
// TestValidateAgeRecipients checks that recipients are parsed at config load
// (a bad entry fails immediately, not mid-backup) and that no invalid entry —
// least of all a pasted secret key — is echoed in the error. An empty list
@@ -522,6 +582,7 @@ func TestValidateAgeRecipients(t *testing.T) {
AgeRecipients: recipients,
Snapshots: map[string]SnapshotConfig{"test": {Paths: []string{"/tmp/src"}}},
StorageURL: "file:///tmp/vaultik-test-store",
IndexPath: "/tmp/vaultik-test-index.sqlite",
ChunkSize: Size(10 * 1024 * 1024),
BlobSizeLimit: Size(10 * 1024 * 1024 * 1024),
CompressionLevel: 3,