MCP: another platform's absolute path is outside the read roots everywhere

Outside Windows, a path that starts with a drive (`C:`) or a `\` is read
as a name in the working folder, so with the working folder inside a
read root it passed the read check. ui.inspect then kept it in recorded
action steps and replies did not hide it when a request sent it. The
check now counts such a path as outside the read roots wherever the
control channel and the library commands check a path.
This commit is contained in:
dotCooCoo
2026-11-12 07:17:46 -08:00
parent 6d7a8249a7
commit 97b77fe4fd
4 changed files with 29 additions and 13 deletions
+4 -11
View File
@@ -472,17 +472,10 @@ fn file_name(path: &str) -> String {
}
/// May the request being served read `path` ([`crate::file_access::check_read`])? Always, without
/// roots in force.
/// roots in force. Another platform's absolute path ([`crate::file_access::written_elsewhere`]) is
/// never readable under a read root.
fn readable(path: &str) -> bool {
crate::file_access::check_read(path).is_ok()
}
/// Is `path`, under a read root, written as an absolute path of another platform: outside Windows, one
/// that starts with a drive (`C:`) or a `\`? The file access check would read it as a name in the
/// working folder.
fn written_elsewhere(path: &str) -> bool {
let foreign = path.starts_with('\\') || matches!(path.as_bytes(), [drive, b':', ..] if drive.is_ascii_alphabetic());
!cfg!(windows) && foreign && crate::file_access::current().is_some()
!crate::file_access::written_elsewhere(path) && crate::file_access::check_read(path).is_ok()
}
/// The loaded library ids (`loaded/<path>`) whose file the request being served may not read, each
@@ -514,7 +507,7 @@ pub fn concealed_library_ids(s: &Session, others: impl IntoIterator<Item = Strin
(name, path)
});
match name {
Some((name, path)) if name != path && (written_elsewhere(path) || !readable(path)) => hidden.push((id, name)),
Some((name, path)) if name != path && !readable(path) => hidden.push((id, name)),
_ => {
taken.insert(id);
}
+7
View File
@@ -215,6 +215,13 @@ pub fn check_read(path: &str) -> Result<(), String> {
current().map_or(Ok(()), |r| r.check(path, Access::Read))
}
/// Is `path`, under a read root, written as an absolute path of another platform: outside Windows, one
/// that starts with a drive (`C:`) or a `\`? [`check_read`] reads it as a name in the working folder.
pub fn written_elsewhere(path: &str) -> bool {
let foreign = path.starts_with('\\') || matches!(path.as_bytes(), [drive, b':', ..] if drive.is_ascii_alphabetic());
!cfg!(windows) && foreign && current().is_some()
}
/// Does a command param named `key` hold a file or folder path: `path`, `paths`, `file`, `files`,
/// `folder`, or a name ending in `Path`, `Paths`, `File`, `Files` or `Folder` (`diffPath`,
/// `destinationFolder`)?
+3 -2
View File
@@ -824,9 +824,10 @@ const SCRATCH_CHOICES: [&str; 2] = ["Startup", "None"];
/// May the request being served read `path`? Under an automation read root (a control client's
/// request), `ui.inspect` leaves out a path outside the root, as `ui.menu.list` leaves out a recent
/// file or a listed script outside it.
/// file or a listed script outside it. Another platform's absolute path
/// ([`vectorcraft_engine::file_access::written_elsewhere`]) is never readable under a read root.
fn readable(path: &str) -> bool {
vectorcraft_engine::file_access::check_read(path).is_ok()
!vectorcraft_engine::file_access::written_elsewhere(path) && vectorcraft_engine::file_access::check_read(path).is_ok()
}
/// Is `s` a path the request may not read, written as an absolute path (either separator, with or
+15
View File
@@ -263,6 +263,21 @@ fn a_library_saved_with_another_platforms_path_is_hidden() {
assert_eq!(hiding.hidden, [(r"loaded/C:\Users\someone\b.svg".to_string(), "loaded/b.svg".to_string())], "{hiding:?}");
}
/// Another platform's absolute path is outside the read root, also when the working folder is inside
/// it: a request that sends one sees it by its file name.
#[test]
fn another_platforms_absolute_path_is_unreadable() {
let here = std::env::current_dir().unwrap();
let roots = vectorcraft_engine::file_access::AutomationRoots::new(&[&here], &[]).unwrap();
let path = r"C:\Users\someone\private.svg";
let mut sent = Vec::new();
vectorcraft_engine::file_access::confine(roots.as_ref(), || {
assert!(!super::readable(path) && super::unreadable_absolute(path));
super::sent_paths(&json!({"list": [path]}), &mut sent);
});
assert_eq!(sent, [(format!("loaded/{path}"), "loaded/private.svg".to_string())]);
}
/// A hidden relative path is hidden where it stands alone or after `./`, and stays where it ends a
/// longer path.
#[test]