Vitess users with backup storage access can write to arbitrary file paths on restore
Description
Vitess is a database clustering system for horizontal scaling of MySQL. Prior to versions 23.0.3 and 22.0.4, anyone with read/write access to the backup storage location (e.g. an S3 bucket) can manipulate backup manifest files so that files in the manifest — which may be files that they have also added to the manifest and backup contents — are written to any accessible location on restore. This is a common path traversal security issue. This can be used to provide that attacker with unintended/unauthorized access to the production deployment environment — allowing them to access information available in that environment as well as run any additional arbitrary commands there. Versions 23.0.3 and 22.0.4 contain a patch. No known workarounds are available.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Vitess backup restore allows path traversal via manipulated manifest files, enabling attackers with storage access to write files to arbitrary locations.
Vulnerability
Vitess backup and restore process trusts the content of backup manifest files (MANIFEST) stored in backup storage locations such as S3 buckets. An attacker with read/write access to that storage can craft a manifest containing path traversal sequences (e.g., ../). During restore, the software constructs file paths by combining base directories with names from the manifest without proper validation, leading to arbitrary file write outside the intended restore directory [1]. This is a classic path traversal security issue [2].
Exploitation
To exploit this vulnerability, an attacker must have read and write access to the backup storage location (e.g., an S3 bucket) used by Vitess. They can modify the MANIFEST file to include entries with path traversal sequences and add corresponding files to the backup contents. No additional authentication or privileges on the Vitess cluster are required; the traversal occurs when the backup is restored, for example during disaster recovery procedures [1].
Impact
Successful exploitation allows the attacker to write arbitrary files to any accessible path on the filesystem of the target environment where the restore is performed. This can lead to unauthorized access to sensitive information, overwriting critical configuration files, or placement of malicious executables that enable further compromise. The patch notes indicate that this could allow the attacker to run arbitrary commands in the production deployment environment [1].
Mitigation
The vulnerability is fixed in Vitess versions 23.0.3 and 22.0.4. The fix adds validation in the backup engine to reject file entries that would escape the designated base directory using path traversal [3][4]. The commit introduces a fileutil.ErrInvalidJoinedPath error for such attempts. No known workarounds exist, so upgrading to a patched version is strongly recommended [1].
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 |
|---|---|---|
vitess.io/vitessGo | >= 0.23.0-rc1, < 0.23.3 | 0.23.3 |
vitess.io/vitessGo | < 0.22.4 | 0.22.4 |
Affected products
2- vitessio/vitessv5Range: < 22.0.4
Patches
1c565cab615bc`backupengine`: disallow path traversals via backup `MANIFEST` on restore (#19470)
2 files changed · +94 −2
go/vt/mysqlctl/builtinbackupengine.go+5 −2 modified@@ -36,6 +36,7 @@ import ( "github.com/spf13/pflag" "golang.org/x/sync/errgroup" + "vitess.io/vitess/go/fileutil" "vitess.io/vitess/go/ioutil" "vitess.io/vitess/go/mysql" "vitess.io/vitess/go/mysql/replication" @@ -180,7 +181,9 @@ func registerBuiltinBackupEngineFlags(fs *pflag.FlagSet) { fs.StringVar(&builtinIncrementalRestorePath, "builtinbackup-incremental-restore-path", builtinIncrementalRestorePath, "the directory where incremental restore files, namely binlog files, are extracted to. In k8s environments, this should be set to a directory that is shared between the vttablet and mysqld pods. The path should exist. When empty, the default OS temp dir is assumed.") } -// fullPath returns the full path of the entry, based on its type +// fullPath returns the full path of the entry, based on its type. +// It validates that the resolved path does not escape the base directory +// via path traversal (e.g. "../../" sequences in fe.Name). func (fe *FileEntry) fullPath(cnf *Mycnf) (string, error) { // find the root to use var root string @@ -197,7 +200,7 @@ func (fe *FileEntry) fullPath(cnf *Mycnf) (string, error) { return "", vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "unknown base: %v", fe.Base) } - return path.Join(fe.ParentPath, root, fe.Name), nil + return fileutil.SafePathJoin(path.Join(fe.ParentPath, root), fe.Name) } // open attempts to open the file
go/vt/mysqlctl/builtinbackupengine_test.go+89 −0 modified@@ -21,7 +21,9 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "vitess.io/vitess/go/fileutil" tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" ) @@ -70,6 +72,93 @@ func TestGetIncrementalFromPosGTIDSet(t *testing.T) { } } +func TestFileEntryFullPath(t *testing.T) { + cnf := &Mycnf{ + DataDir: "/vt/data", + InnodbDataHomeDir: "/vt/innodb-data", + InnodbLogGroupHomeDir: "/vt/innodb-log", + BinLogPath: "/vt/binlogs/mysql-bin", + } + + tests := []struct { + name string + entry FileEntry + wantPath string + wantError error + }{ + { + name: "valid relative path in DataDir", + entry: FileEntry{Base: backupData, Name: "mydb/table1.ibd"}, + wantPath: "/vt/data/mydb/table1.ibd", + }, + { + name: "valid relative path in InnodbDataHomeDir", + entry: FileEntry{Base: backupInnodbDataHomeDir, Name: "ibdata1"}, + wantPath: "/vt/innodb-data/ibdata1", + }, + { + name: "valid relative path in InnodbLogGroupHomeDir", + entry: FileEntry{Base: backupInnodbLogGroupHomeDir, Name: "ib_logfile0"}, + wantPath: "/vt/innodb-log/ib_logfile0", + }, + { + name: "valid relative path in BinlogDir", + entry: FileEntry{Base: backupBinlogDir, Name: "mysql-bin.000001"}, + wantPath: "/vt/binlogs/mysql-bin.000001", + }, + { + name: "valid path with ParentPath", + entry: FileEntry{Base: backupData, Name: "mydb/table1.ibd", ParentPath: "/tmp/restore"}, + wantPath: "/tmp/restore/vt/data/mydb/table1.ibd", + }, + { + name: "path traversal escapes base directory", + entry: FileEntry{Base: backupData, Name: "../../etc/passwd"}, + wantError: fileutil.ErrInvalidJoinedPath, + }, + { + name: "path traversal with deeper nesting", + entry: FileEntry{Base: backupData, Name: "mydb/../../../etc/shadow"}, + wantError: fileutil.ErrInvalidJoinedPath, + }, + { + name: "path traversal to root", + entry: FileEntry{Base: backupData, Name: "../../../../../etc/crontab"}, + wantError: fileutil.ErrInvalidJoinedPath, + }, + { + name: "path traversal escapes ParentPath", + entry: FileEntry{Base: backupData, Name: "../../../../etc/passwd", ParentPath: "/tmp/restore"}, + wantError: fileutil.ErrInvalidJoinedPath, + }, + { + name: "relative path with dot-dot that stays within base", + entry: FileEntry{Base: backupData, Name: "mydb/../mydb/table1.ibd"}, + wantPath: "/vt/data/mydb/table1.ibd", + }, + } + + // Test unknown base separately since it returns a different error type. + t.Run("unknown base", func(t *testing.T) { + entry := FileEntry{Base: "unknown", Name: "file"} + _, err := entry.fullPath(cnf) + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown base") + }) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.entry.fullPath(cnf) + if tt.wantError != nil { + require.ErrorIs(t, err, tt.wantError) + } else { + require.NoError(t, err) + assert.Equal(t, tt.wantPath, got) + } + }) + } +} + func TestShouldDrainForBackupBuiltIn(t *testing.T) { be := &BuiltinBackupEngine{}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-r492-hjgh-c9gwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-27969ghsaADVISORY
- github.com/vitessio/vitess/commit/c565cab615bc962bda061dcd645aa7506c59ca4aghsax_refsource_MISCWEB
- github.com/vitessio/vitess/pull/19470ghsax_refsource_MISCWEB
- github.com/vitessio/vitess/security/advisories/GHSA-r492-hjgh-c9gwghsax_refsource_CONFIRMWEB
- owasp.org/www-community/attacks/Path_TraversalghsaWEB
News mentions
0No linked articles in our index yet.