Skip to content

Commit 91a87bc

Browse files
pmorris-devclaude
andcommitted
fix: auto-create intermediate directories for nested database paths
The path traversal validation introduced in e58189d canonicalizes the parent directory to verify containment, but this fails with an IO error when intermediate directories in a relative path (e.g. "subdir/mydb.db") do not yet exist. The previous implementation returned the joined path without requiring the parent to exist. Call create_dir_all on the parent before canonicalizing so that nested relative paths work without the caller needing to pre-create subdirectories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 10f6f71 commit 91a87bc

1 file changed

Lines changed: 20 additions & 6 deletions

File tree

src/resolve.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,18 @@ fn validate_and_resolve(path: &str, base: &Path) -> Result<PathBuf, Error> {
8383
let canonical_resolved = if joined.exists() {
8484
joined.canonicalize()
8585
} else {
86-
// Canonicalize the parent and re-append the file name
87-
joined
86+
// Ensure intermediate directories exist so that canonicalize can resolve the
87+
// parent. This matches the caller's expectation that nested relative paths like
88+
// "subdir/mydb.db" work without pre-creating "subdir/".
89+
let parent = joined
8890
.parent()
89-
.ok_or_else(|| Error::InvalidPath("path has no parent".to_string()))?
91+
.ok_or_else(|| Error::InvalidPath("path has no parent".to_string()))?;
92+
93+
create_dir_all(parent)?;
94+
95+
parent
9096
.canonicalize()
91-
.map(|parent| parent.join(joined.file_name().unwrap_or_default()))
97+
.map(|p| p.join(joined.file_name().unwrap_or_default()))
9298
}
9399
.map_err(|e| Error::InvalidPath(format!("cannot canonicalize path: {e}")))?;
94100

@@ -135,10 +141,18 @@ mod tests {
135141
#[test]
136142
fn test_subdirectory_path() {
137143
let base = make_temp_base();
138-
// Create the subdirectory so canonicalize can resolve it
139-
fs::create_dir_all(base.join("subdir")).unwrap();
144+
// Intermediate directories are auto-created — no manual setup needed
140145
let result = validate_and_resolve("subdir/mydb.db", &base).unwrap();
141146
assert_eq!(result, base.join("subdir/mydb.db"));
147+
assert!(base.join("subdir").is_dir());
148+
}
149+
150+
#[test]
151+
fn test_nested_subdirectory_path() {
152+
let base = make_temp_base();
153+
let result = validate_and_resolve("a/b/c/mydb.db", &base).unwrap();
154+
assert_eq!(result, base.join("a/b/c/mydb.db"));
155+
assert!(base.join("a/b/c").is_dir());
142156
}
143157

144158
#[test]

0 commit comments

Comments
 (0)