Skip to content

Commit 27743aa

Browse files
chaliyclaude
andauthored
fix(fs): add validate_path to all InMemoryFs methods (#460)
## Summary - Added `validate_path()` calls to: remove, stat, read_dir, exists, rename, copy, symlink, read_link, chmod - `copy()` now also checks write limits before creating entries - Previously only read_file, write_file, append_file, mkdir had validation ## Test plan - [x] `test_validate_path_on_copy` — deep path rejected - [x] `test_validate_path_on_rename` — deep path rejected - [x] `test_copy_respects_write_limits` — file count limit enforced - [x] `test_validate_path_on_chmod` — deep path rejected - [x] All 1471 lib tests pass Closes #421 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9364b39 commit 27743aa

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

crates/bashkit/src/fs/memory.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,9 @@ impl FileSystem for InMemoryFs {
10311031
}
10321032

10331033
async fn remove(&self, path: &Path, recursive: bool) -> Result<()> {
1034+
self.limits
1035+
.validate_path(path)
1036+
.map_err(|e| IoError::other(e.to_string()))?;
10341037
let path = Self::normalize_path(path);
10351038
let mut entries = self.entries.write().unwrap();
10361039

@@ -1072,6 +1075,9 @@ impl FileSystem for InMemoryFs {
10721075
}
10731076

10741077
async fn stat(&self, path: &Path) -> Result<Metadata> {
1078+
self.limits
1079+
.validate_path(path)
1080+
.map_err(|e| IoError::other(e.to_string()))?;
10751081
let path = Self::normalize_path(path);
10761082
let entries = self.entries.read().unwrap();
10771083

@@ -1084,6 +1090,9 @@ impl FileSystem for InMemoryFs {
10841090
}
10851091

10861092
async fn read_dir(&self, path: &Path) -> Result<Vec<DirEntry>> {
1093+
self.limits
1094+
.validate_path(path)
1095+
.map_err(|e| IoError::other(e.to_string()))?;
10871096
let path = Self::normalize_path(path);
10881097
let entries = self.entries.read().unwrap();
10891098

@@ -1116,12 +1125,21 @@ impl FileSystem for InMemoryFs {
11161125
}
11171126

11181127
async fn exists(&self, path: &Path) -> Result<bool> {
1128+
self.limits
1129+
.validate_path(path)
1130+
.map_err(|e| IoError::other(e.to_string()))?;
11191131
let path = Self::normalize_path(path);
11201132
let entries = self.entries.read().unwrap();
11211133
Ok(entries.contains_key(&path))
11221134
}
11231135

11241136
async fn rename(&self, from: &Path, to: &Path) -> Result<()> {
1137+
self.limits
1138+
.validate_path(from)
1139+
.map_err(|e| IoError::other(e.to_string()))?;
1140+
self.limits
1141+
.validate_path(to)
1142+
.map_err(|e| IoError::other(e.to_string()))?;
11251143
let from = Self::normalize_path(from);
11261144
let to = Self::normalize_path(to);
11271145
let mut entries = self.entries.write().unwrap();
@@ -1135,6 +1153,12 @@ impl FileSystem for InMemoryFs {
11351153
}
11361154

11371155
async fn copy(&self, from: &Path, to: &Path) -> Result<()> {
1156+
self.limits
1157+
.validate_path(from)
1158+
.map_err(|e| IoError::other(e.to_string()))?;
1159+
self.limits
1160+
.validate_path(to)
1161+
.map_err(|e| IoError::other(e.to_string()))?;
11381162
let from = Self::normalize_path(from);
11391163
let to = Self::normalize_path(to);
11401164
let mut entries = self.entries.write().unwrap();
@@ -1144,11 +1168,24 @@ impl FileSystem for InMemoryFs {
11441168
.cloned()
11451169
.ok_or_else(|| IoError::new(ErrorKind::NotFound, "not found"))?;
11461170

1171+
// Check write limits before creating the copy
1172+
let entry_size = match &entry {
1173+
FsEntry::File { content, .. } => content.len() as u64,
1174+
_ => 0,
1175+
};
1176+
let is_new = !entries.contains_key(&to);
1177+
if is_new {
1178+
self.check_write_limits(&entries, &to, entry_size as usize)?;
1179+
}
1180+
11471181
entries.insert(to, entry);
11481182
Ok(())
11491183
}
11501184

11511185
async fn symlink(&self, target: &Path, link: &Path) -> Result<()> {
1186+
self.limits
1187+
.validate_path(link)
1188+
.map_err(|e| IoError::other(e.to_string()))?;
11521189
let link = Self::normalize_path(link);
11531190
let mut entries = self.entries.write().unwrap();
11541191

@@ -1170,6 +1207,9 @@ impl FileSystem for InMemoryFs {
11701207
}
11711208

11721209
async fn read_link(&self, path: &Path) -> Result<PathBuf> {
1210+
self.limits
1211+
.validate_path(path)
1212+
.map_err(|e| IoError::other(e.to_string()))?;
11731213
let path = Self::normalize_path(path);
11741214
let entries = self.entries.read().unwrap();
11751215

@@ -1181,6 +1221,9 @@ impl FileSystem for InMemoryFs {
11811221
}
11821222

11831223
async fn chmod(&self, path: &Path, mode: u32) -> Result<()> {
1224+
self.limits
1225+
.validate_path(path)
1226+
.map_err(|e| IoError::other(e.to_string()))?;
11841227
let path = Self::normalize_path(path);
11851228
let mut entries = self.entries.write().unwrap();
11861229

@@ -1686,4 +1729,63 @@ mod tests {
16861729
let result = fs.append_file(Path::new("/tmp/dir"), b"data").await;
16871730
assert!(result.is_err());
16881731
}
1732+
1733+
// Issue #421: validate_path should be called on all methods
1734+
#[tokio::test]
1735+
async fn test_validate_path_on_copy() {
1736+
let limits = FsLimits::new().max_path_depth(3);
1737+
let fs = InMemoryFs::with_limits(limits);
1738+
fs.write_file(Path::new("/tmp/src.txt"), b"data")
1739+
.await
1740+
.unwrap();
1741+
1742+
let deep = Path::new("/a/b/c/d/e/f.txt");
1743+
let result = fs.copy(Path::new("/tmp/src.txt"), deep).await;
1744+
assert!(result.is_err(), "copy to deep path should be rejected");
1745+
}
1746+
1747+
#[tokio::test]
1748+
async fn test_validate_path_on_rename() {
1749+
let limits = FsLimits::new().max_path_depth(3);
1750+
let fs = InMemoryFs::with_limits(limits);
1751+
fs.write_file(Path::new("/tmp/src.txt"), b"data")
1752+
.await
1753+
.unwrap();
1754+
1755+
let deep = Path::new("/a/b/c/d/e/f.txt");
1756+
let result = fs.rename(Path::new("/tmp/src.txt"), deep).await;
1757+
assert!(result.is_err(), "rename to deep path should be rejected");
1758+
}
1759+
1760+
#[tokio::test]
1761+
async fn test_copy_respects_write_limits() {
1762+
let limits = FsLimits::new().max_file_count(10);
1763+
let fs = InMemoryFs::with_limits(limits);
1764+
1765+
// Fill up to limit
1766+
for i in 0..10 {
1767+
let _ = fs
1768+
.write_file(Path::new(&format!("/tmp/f{i}.txt")), b"x")
1769+
.await;
1770+
}
1771+
1772+
// Copy should fail - at file count limit
1773+
let result = fs
1774+
.copy(Path::new("/tmp/f0.txt"), Path::new("/tmp/copy.txt"))
1775+
.await;
1776+
assert!(
1777+
result.is_err(),
1778+
"copy should respect file count write limits"
1779+
);
1780+
}
1781+
1782+
#[tokio::test]
1783+
async fn test_validate_path_on_chmod() {
1784+
let limits = FsLimits::new().max_path_depth(3);
1785+
let fs = InMemoryFs::with_limits(limits);
1786+
1787+
let deep = Path::new("/a/b/c/d/e/f.txt");
1788+
let result = fs.chmod(deep, 0o755).await;
1789+
assert!(result.is_err(), "chmod on deep path should be rejected");
1790+
}
16891791
}

0 commit comments

Comments
 (0)