CVE-2025-67818
Description
An issue was discovered in Weaviate OSS before 1.33.4. An attacker with access to insert data into the database can craft an entry name with an absolute path (e.g., /etc/...) or use parent directory traversal (../../..) to escape the restore root when a backup is restored, potentially creating or overwriting files in arbitrary locations within the application's privilege scope.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Weaviate OSS before 1.33.4 has a path traversal vulnerability in backup restore allowing authenticated attackers to create or overwrite arbitrary files.
Vulnerability
Description
An issue discovered in Weaviate OSS before 1.33.4 allows an attacker with access to insert data into the database to craft an entry name using absolute paths (e.g., /etc/...) or parent directory traversal sequences (../../..). When a backup is restored, these malicious entry names escape the intended restore root, potentially creating or overwriting files in arbitrary locations within the application's privilege scope [2].
Exploitation
Exploitation requires the attacker to have the ability to insert data into the Weaviate database, which may be achieved through legitimate user accounts or compromised credentials. The attack is triggered during the backup restoration process, where the crafted entry names are processed without proper sanitization [4]. No special network position is required beyond access to the database API.
Impact
Successful exploitation allows an attacker to write or overwrite files anywhere the Weaviate process has permissions, potentially leading to arbitrary code execution, configuration tampering, or data corruption. The vulnerability has been assigned a CVSS score of 7.2 (High) [4].
Mitigation
Weaviate has released patches for versions 1.30.x, 1.31.x, 1.32.x, and 1.33.x. Users are advised to upgrade to version 1.33.4 or later. As a workaround, the backup modules can be disabled by removing any backup* entries from the enabled_modules configuration flag [4]. Customers using Weaviate Cloud or marketplace deployments have been patched automatically.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/weaviate/weaviateGo | < 1.30.20 | 1.30.20 |
github.com/weaviate/weaviateGo | >= 1.31.0-rc.0, < 1.31.19 | 1.31.19 |
github.com/weaviate/weaviateGo | >= 1.32.0-rc.0, < 1.32.16 | 1.32.16 |
github.com/weaviate/weaviateGo | >= 1.33.0-rc.0, < 1.33.4 | 1.33.4 |
Affected products
2Patches
289c2270869e6Fix path breakout in backup
4 files changed · +80 −6
entities/diskio/files.go+33 −1 modified@@ -11,7 +11,12 @@ package diskio -import "os" +import ( + "fmt" + "os" + "path/filepath" + "strings" +) func FileExists(file string) (bool, error) { _, err := os.Stat(file) @@ -33,3 +38,30 @@ func Fsync(path string) error { return f.Sync() } + +// SanitizeFilePathJoin joins a root path and a relative file path, ensuring that the resulting path is within the root +// path. It assumes that the relativeFilePath is attacker controlled. +func SanitizeFilePathJoin(rootPath string, relativeFilePath string) (string, error) { + // Resolve symlinks in root path + rootPath, err := filepath.EvalSymlinks(rootPath) + if err != nil { + return "", fmt.Errorf("resolve symlinks for root path %q: %w", rootPath, err) + } + + // clean the path to remove any ../ or ./ sequences + cleanFilePath := filepath.Clean(relativeFilePath) + if filepath.IsAbs(cleanFilePath) { + return "", fmt.Errorf("relative file path %q is an absolute path", relativeFilePath) + } + combinedPath := filepath.Join(rootPath, cleanFilePath) + finalPath := filepath.Clean(combinedPath) + + rel, err := filepath.Rel(rootPath, finalPath) + if err != nil { + return "", fmt.Errorf("make %q relative to %q: %w", finalPath, rootPath, err) + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("file path %q is outside shard root %q", finalPath, rootPath) + } + return finalPath, nil +}
entities/diskio/files_test.go+38 −0 added@@ -0,0 +1,38 @@ +package diskio + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSanitizeFilePathJoin(t *testing.T) { + tests := []struct { + name string + relative string + wantErr bool + }{ + {name: "valid relative", relative: "sub/file.txt", wantErr: false}, + {name: "escape with dot-dot", relative: filepath.Join("..", "outside", "out.txt"), wantErr: true}, + {name: "absolute path rejected", relative: filepath.Join(string(filepath.Separator), "etc", "passwd"), wantErr: true}, + {name: "normalized traversal inside root", relative: filepath.Join("sub", "..", "sub", "file.txt"), wantErr: false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + root := t.TempDir() + got, err := SanitizeFilePathJoin(root, tc.relative) + + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + rootPath, err := filepath.EvalSymlinks(root) + require.NoError(t, err) + + require.Equal(t, filepath.Join(rootPath, "sub", "file.txt"), got) + }) + } +}
usecases/backup/zip.go+8 −4 modified@@ -26,6 +26,7 @@ import ( "time" "github.com/weaviate/weaviate/entities/backup" + "github.com/weaviate/weaviate/entities/diskio" ) // CompressionLevel represents supported compression level @@ -101,7 +102,7 @@ func (z *zip) WriteShard(ctx context.Context, sd *backup.ShardDescriptor) (writt n, err = z.WriteRegulars(ctx, sd.Files) written += n - return + return written, err } func (z *zip) WriteRegulars(ctx context.Context, relPaths []string) (written int64, err error) { @@ -170,7 +171,7 @@ func (z *zip) writeOne(ctx context.Context, info fs.FileInfo, relPath string, r } return written, fmt.Errorf("copy: %s %w", relPath, err) } - return + return written, err } // lastWritten number of bytes @@ -239,7 +240,10 @@ func (u *unzip) ReadChunk() (written int64, err error) { } // target file - target := filepath.Join(u.destPath, header.Name) + target, err := diskio.SanitizeFilePathJoin(u.destPath, header.Name) + if err != nil { + return written, fmt.Errorf("sanitize file path %s: %w", header.Name, err) + } switch header.Typeflag { case tar.TypeDir: if err := os.MkdirAll(target, 0o755); err != nil { @@ -295,7 +299,7 @@ type readCloser struct { func (r *readCloser) Read(p []byte) (n int, err error) { n, err = r.src.Read(p) atomic.AddInt64(&r.n, int64(n)) - return + return n, err } func (r *readCloser) Close() error { return r.src.Close() }
usecases/backup/zip_test.go+1 −1 modified@@ -138,7 +138,7 @@ func TestUnzipPathEscape(t *testing.T) { }() _, err = uz.ReadChunk() - require.NoError(t, err) + require.ErrorContains(t, err, "outside shard root") entries, err := os.ReadDir(completelyUnrelatedDir) require.NoError(t, err)
169df2dc92bcMerge pull request #9597 from weaviate/fix_breakout
49 files changed · +124 −66
adapters/handlers/rest/configure_api_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build linux -// +build linux package rest
adapters/repos/classifications/repo_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package classifications
adapters/repos/db/aggregations_fixtures_for_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package db
adapters/repos/db/clusterintegrationtest/cluster_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package clusterintegrationtest
adapters/repos/db/index_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package db
adapters/repos/db/lsmkv/bucket_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/cleanup_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/cleanup_replace_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/commitlogger_parser_strategy_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/compaction_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/compaction_roaring_set_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/compaction_roaring_set_range_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/compaction_set_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/compactor_inverted_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/concurrent_writing_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/helper_for_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/recover_from_wal_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/store_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/strategies_map_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/strategies_replace_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/strategies_roaringset_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/strategies_roaringsetrange_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/lsmkv/strategies_set_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package lsmkv
adapters/repos/db/merge_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package db
adapters/repos/db/multi_shard_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package db
adapters/repos/db/nodes_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package db
adapters/repos/db/shard_backup.go+38 −2 modified@@ -17,6 +17,7 @@ import ( "io" "os" "path/filepath" + "strings" "time" "github.com/weaviate/weaviate/entities/backup" @@ -283,7 +284,10 @@ func (s *Shard) GetFileMetadata(ctx context.Context, relativeFilePath string) (f s.mayResetInactivityTimer() - finalPath := filepath.Join(s.Index().Config.RootPath, relativeFilePath) + finalPath, err := s.sanitizeFilePath(relativeFilePath) + if err != nil { + return file.FileMetadata{}, fmt.Errorf("sanitize file path %q: %w", relativeFilePath, err) + } return file.GetFileMetadata(finalPath) } @@ -298,7 +302,10 @@ func (s *Shard) GetFile(ctx context.Context, relativeFilePath string) (io.ReadCl s.mayResetInactivityTimer() - finalPath := filepath.Join(s.Index().Config.RootPath, relativeFilePath) + finalPath, err := s.sanitizeFilePath(relativeFilePath) + if err != nil { + return nil, fmt.Errorf("sanitize file path %q: %w", relativeFilePath, err) + } reader, err := os.Open(finalPath) if err != nil { @@ -307,3 +314,32 @@ func (s *Shard) GetFile(ctx context.Context, relativeFilePath string) (io.ReadCl return reader, nil } + +func (s *Shard) sanitizeFilePath(relativeFilePath string) (string, error) { + // clean the path to remove any ../ or ./ sequences + cleanFilePath := filepath.Clean(relativeFilePath) + if filepath.IsAbs(cleanFilePath) { + return "", fmt.Errorf("relative file path %q is an absolute path", relativeFilePath) + } + combinedPath := filepath.Join(s.index.Config.RootPath, cleanFilePath) + finalPath, err := filepath.EvalSymlinks(combinedPath) + if err != nil { + return "", fmt.Errorf("resolve symlinks for %q: %w", finalPath, err) + } + finalPath = filepath.Clean(finalPath) + + // Resolve symlinks in root path - this is important for testing on MacOs where /var is a symlink + rootPath, err := filepath.EvalSymlinks(s.index.Config.RootPath) + if err != nil { + return "", fmt.Errorf("resolve symlinks for root path %q: %w", s.index.Config.RootPath, err) + } + + rel, err := filepath.Rel(rootPath, finalPath) + if err != nil { + return "", fmt.Errorf("make %q relative to %q: %w", finalPath, rootPath, err) + } + if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return "", fmt.Errorf("file path %q is outside shard root %q", finalPath, rootPath) + } + return finalPath, nil +}
adapters/repos/db/shard_path_test.go+71 −0 added@@ -0,0 +1,71 @@ +// _ _ +// __ _____ __ ___ ___ __ _| |_ ___ +// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ +// \ V V / __/ (_| |\ V /| | (_| | || __/ +// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| +// +// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. +// +// CONTACT: hello@weaviate.io +// + +package db + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/weaviate/weaviate/entities/additional" + "github.com/weaviate/weaviate/entities/backup" +) + +func TestShardFileSanitize(t *testing.T) { + // create a secret path that would be used in a malicious file path + secretPath := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(secretPath, "secret.txt"), []byte("secret"), 0o600)) + + ctx := testCtx() + className := "TestClass" + shd, idx := testShard(t, ctx, className) + require.NoError(t, shd.HaltForTransfer(ctx, false, 100*time.Millisecond)) + amount := 10 + + for range amount { + obj := testObject(className) + + err := shd.PutObject(ctx, obj) + require.Nil(t, err) + } + + objs, err := shd.ObjectList(ctx, amount, nil, nil, additional.Properties{}, shd.Index().Config.ClassName) + require.Nil(t, err) + require.Equal(t, amount, len(objs)) + + // try to read outside of the shard directory + _, err = shd.GetFile(ctx, "../001/secret.txt") + require.Error(t, err) + _, err = shd.GetFileMetadata(ctx, "../001/secret.txt") + require.Error(t, err) + + // create a second "fake" index and shard and try to read it + otherShardDir := filepath.Join(idx.Config.RootPath, "otherIndex", "otherShard") + require.NoError(t, os.MkdirAll(otherShardDir, 0o700)) + require.NoError(t, os.WriteFile(filepath.Join(otherShardDir, "secret.txt"), []byte("secret"), 0o700)) + + file, err := shd.GetFile(ctx, filepath.Join(otherShardDir, "secret.txt")) + require.Error(t, err) + require.Nil(t, file) + _, err = shd.GetFileMetadata(ctx, filepath.Join(otherShardDir, "secret.txt")) + require.Error(t, err) + + // now read a valid file + ret := &backup.ShardDescriptor{} + require.NoError(t, shd.ListBackupFiles(ctx, ret)) + + file, err = shd.GetFile(ctx, ret.ShardVersionPath) + require.NoError(t, err) + require.NotNil(t, file) +}
adapters/repos/db/vector/hnsw/backup_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package hnsw
adapters/repos/db/vector/hnsw/compress_sift_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build benchmarkSiftRecall -// +build benchmarkSiftRecall package hnsw_test
adapters/repos/db/vector/hnsw/condensor_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package hnsw
adapters/repos/db/vector/hnsw/distancer/asm/dot.go+1 −7 modified@@ -9,17 +9,11 @@ // CONTACT: hello@weaviate.io // +//nolint:govet //go:build ignore -// +build ignore package main -import ( - . "github.com/mmcloughlin/avo/build" - . "github.com/mmcloughlin/avo/operand" - . "github.com/mmcloughlin/avo/reg" -) - var unroll = 4 // inspired by the avo example which is itself inspired by the PeachPy example.
adapters/repos/db/vector/hnsw/distancer/asm/l2.go+1 −7 modified@@ -9,17 +9,11 @@ // CONTACT: hello@weaviate.io // +//nolint:govet //go:build ignore -// +build ignore package main -import ( - . "github.com/mmcloughlin/avo/build" - . "github.com/mmcloughlin/avo/operand" - . "github.com/mmcloughlin/avo/reg" -) - var unroll = 4 func main() {
adapters/repos/db/vector/hnsw/distancer/asm/prefetch.go+1 −7 modified@@ -9,17 +9,11 @@ // CONTACT: hello@weaviate.io // +//nolint:govet //go:build ignore -// +build ignore package main -import ( - . "github.com/mmcloughlin/avo/build" - . "github.com/mmcloughlin/avo/operand" - // . "github.com/mmcloughlin/avo/reg" -) - func main() { TEXT("Prefetch", NOSPLIT, "func(addr uintptr)") addr := Mem{Base: Load(Param("addr"), GP64())}
adapters/repos/db/vector/hnsw/generate_recall_datasets.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build ignore -// +build ignore package main
adapters/repos/db/vector/hnsw/index_corrupt_commitlogs_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package hnsw
adapters/repos/db/vector/hnsw/index_slowdown_bug_intergration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTestBug -// +build integrationTestBug package hnsw
adapters/repos/db/vector/hnsw/index_too_many_links_bug_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest && !race -// +build integrationTest,!race package hnsw
adapters/repos/db/vector/hnsw/multivector_hnsw_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package hnsw
adapters/repos/db/vector/hnsw/persistence_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package hnsw
adapters/repos/db/vector/hnsw/recall_geo_spatial_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTestSlow && !race -// +build integrationTestSlow,!race package hnsw
adapters/repos/db/vector/hnsw/recall_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build benchmarkRecall -// +build benchmarkRecall package hnsw
adapters/repos/db/vector/hnsw/unreachability_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package hnsw
adapters/repos/modules/modules_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package modulestorage
.golangci.yml+12 −0 modified@@ -41,6 +41,18 @@ linters: - legacy - std-error-handling rules: + - path: ^deprecations/gen\.go$ + linters: + - staticcheck + - govet + - unused + - forbidigo + - misspell + - bodyclose + - errorlint + - exhaustive + - nolintlint + - errcheck - linters: - staticcheck text: S1034
test/helper/race_off.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build !race -// +build !race package helper
test/helper/race_on.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build race -// +build race package helper
usecases/auth/authorization/docs/generator.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build ignore -// +build ignore package main
usecases/classification/integrationtest/fakes_for_integration_test.go+0 −1 modified@@ -10,7 +10,6 @@ // //go:build integrationTest -// +build integrationTest package classification_integration_test
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-7v39-2hx7-7c43ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-67818ghsaADVISORY
- github.com/weaviate/weaviate/commit/169df2dc92bc232df62e8fab0a20db2e5371f7aaghsaWEB
- github.com/weaviate/weaviate/commit/89c2270869e6d64f5b5276b8626c11cd816c6665ghsaWEB
- weaviate.io/blog/weaviate-security-release-november-2025ghsaWEB
News mentions
0No linked articles in our index yet.