Skip to content

Commit a45bef6

Browse files
fix(Mountain): Improve path security checks and logging for workspace access violations
Signed-off-by: Nikola Hristov <Nikola@PlayForm.Cloud>
1 parent 660a067 commit a45bef6

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

Source/Environment/Utility/PathSecurity.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,57 @@ pub fn IsPathAllowedForAccess(ApplicationState:&ApplicationState, PathToCheck:&P
6363
return Ok(());
6464
}
6565

66+
// Use canonical paths on both sides so that prefix-matching survives
67+
// macOS's `/Volumes/<vol>/...` vs `/private/var/...` resolution and
68+
// any symlinked submodule roots. Cocoon's URI strip yields the user-
69+
// visible path (`/Volumes/CORSAIR/.../Land/Dependency/...`) while the
70+
// workspace folder URL stays as built from `from_directory_path` -
71+
// these can disagree on platforms where the resolved canonical path
72+
// differs from the URI-derived one (encoded mount-point indirection,
73+
// case-insensitive HFS+, etc.). Without this, a workspace with deep
74+
// submodule trees rejects every read that walks past the first level
75+
// even though the path is a literal descendant of the open folder.
76+
let CanonicalPathToCheck = PathToCheck
77+
.canonicalize()
78+
.unwrap_or_else(|_| PathToCheck.to_path_buf());
6679
let IsAllowed = FoldersGuard.iter().any(|Folder| {
67-
match Folder.URI.to_file_path() {
68-
Ok(FolderPath) => PathToCheck.starts_with(FolderPath),
69-
Err(_) => false,
70-
}
80+
let FolderPath = match Folder.URI.to_file_path() {
81+
Ok(P) => P,
82+
Err(_) => return false,
83+
};
84+
let CanonicalFolderPath = FolderPath
85+
.canonicalize()
86+
.unwrap_or_else(|_| FolderPath.clone());
87+
// Try both canonical-canonical AND raw-raw - either match wins.
88+
PathToCheck.starts_with(&FolderPath)
89+
|| PathToCheck.starts_with(&CanonicalFolderPath)
90+
|| CanonicalPathToCheck.starts_with(&FolderPath)
91+
|| CanonicalPathToCheck.starts_with(&CanonicalFolderPath)
7192
});
7293

7394
if IsAllowed {
7495
Ok(())
7596
} else {
97+
// Surface the comparison details so a workspace-mismatch bug
98+
// (URL-to-path conversion, canonicalisation drift) is debuggable
99+
// without rebuilding. Tag is `vfs` so it appears under the
100+
// default `short` trace set.
101+
let FolderPaths:Vec<String> = FoldersGuard
102+
.iter()
103+
.map(|F| {
104+
F.URI
105+
.to_file_path()
106+
.map(|P| P.display().to_string())
107+
.unwrap_or_else(|_| format!("<bad-uri:{}>", F.URI))
108+
})
109+
.collect();
110+
dev_log!(
111+
"vfs",
112+
"[PathSecurity] reject path={} canonical={} folders=[{}]",
113+
PathToCheck.display(),
114+
CanonicalPathToCheck.display(),
115+
FolderPaths.join(", ")
116+
);
76117
Err(CommonError::FileSystemPermissionDenied {
77118
Path:PathToCheck.to_path_buf(),
78119
Reason:"Path is outside of the registered workspace folders.".to_string(),

Source/Vine/Server/MountainVinegRPCService.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,17 @@ impl MountainService for MountainVinegRPCService {
400400
// returns -32000 so Cocoon's shim can convert it to a
401401
// proper `vscode.FileSystemError.FileNotFound`.
402402
let LowerError = ErrorString.to_lowercase();
403+
// "Path is outside of the registered workspace folders" /
404+
// "Permission denied" responses come from the path-security
405+
// guard in `Environment/Utility/PathSecurity.rs` when an
406+
// extension probes a directory outside the open workspace
407+
// (Svelte's `enableContextMenu` walks every `package.json`
408+
// in the entire workspace tree, including out-of-root
409+
// submodule dependencies). From the extension's perspective
410+
// these are equivalent to "file not present" and must NOT
411+
// count against Cocoon's circuit breaker - a workspace with
412+
// many sibling submodules trips the breaker open within the
413+
// first few hundred ms of activation otherwise.
403414
let LooksLike404 = (MethodName == "FileSystem.ReadFile"
404415
|| MethodName == "FileSystem.Stat"
405416
|| MethodName == "FileSystem.ReadDirectory")
@@ -408,7 +419,10 @@ impl MountainService for MountainVinegRPCService {
408419
|| LowerError.contains("enoent")
409420
|| LowerError.contains("no such file or directory")
410421
|| LowerError.contains("entity not found")
411-
|| LowerError.contains("os error 2"));
422+
|| LowerError.contains("os error 2")
423+
|| LowerError.contains("path is outside of the registered workspace")
424+
|| LowerError.contains("permission denied for operation")
425+
|| LowerError.contains("workspace is not trusted"));
412426
if LooksLike404 {
413427
dev_log!(
414428
"grpc-verbose",

0 commit comments

Comments
 (0)