mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
fix: allow paths below a filesystem-root workspace root (#3277)
Fixes #2808 `validatePath()` builds the allowed prefix as `canonicalRoot + path.sep`. A filesystem root (`/`, or `C:\` on Windows) already ends with the separator, so the prefix becomes `//` (or `C:\\`) and no path below it matches. This makes `--workspace=/`, which the `--allowUnrestrictedPaths` deprecation notice recommends, reject every file write with `Access denied`. The fix appends the separator only when the root doesn't already end with one. Narrower roots behave as before. The new test in `tests/roots.test.ts` sets the workspace root to the filesystem root of the path under test, so it is on the same drive on Windows (that drive mismatch is why the CI failed on the earlier, stale attempt in #2809). It fails on `main` with `Access denied` and passes with the fix. `tests/roots.test.ts` passes locally. --- AI-assistance note: I prepared this change with help from an AI coding assistant, then reviewed it and ran the tests myself.
This commit is contained in:
1 parent
3eb76a6547
commit
296ccef3dd
2 files changed
+23
-1
No files matched your search
+6
-1
@@ -537,9 +537,14 @@ export class McpContext implements Context, BrowserlessContext {
|
||||
|
||||
if (result.status === 'fulfilled') {
|
||||
const canonicalRoot = result.value;
|
||||
// A filesystem root such as `/` or `C:\` already ends with the
|
||||
// separator; appending another one would match no path below it.
|
||||
const rootPrefix = canonicalRoot.endsWith(path.sep)
|
||||
? canonicalRoot
|
||||
: canonicalRoot + path.sep;
|
||||
if (
|
||||
canonicalPath === canonicalRoot ||
|
||||
canonicalPath.startsWith(canonicalRoot + path.sep)
|
||||
canonicalPath.startsWith(rootPrefix)
|
||||
) {
|
||||
allowed = true;
|
||||
break;
|
||||
|
||||
@@ -181,6 +181,23 @@ describe('McpContext path validation escaping', () => {
|
||||
});
|
||||
}
|
||||
|
||||
it('allows paths below a workspace root that is the filesystem root', async () => {
|
||||
const context = await createContext();
|
||||
const targetPath = path.resolve(
|
||||
os.homedir(),
|
||||
'a_very_unlikely_path_name_filesystem_root',
|
||||
);
|
||||
const canonicalPath = await resolveCanonicalPath(targetPath);
|
||||
// Use the root of the canonical path so the test also holds when the home
|
||||
// directory goes through a symlink or junction onto another drive.
|
||||
const filesystemRoot = path.parse(canonicalPath).root;
|
||||
context.setRoots([
|
||||
{uri: pathToFileURL(filesystemRoot).href, name: 'filesystem root'},
|
||||
]);
|
||||
const resolved = await context.validatePath(targetPath);
|
||||
assert.strictEqual(resolved, canonicalPath);
|
||||
});
|
||||
|
||||
it('escapes the file path when it cannot be resolved', async () => {
|
||||
const context = await createContext();
|
||||
const filePath = path.join(os.tmpdir(), 'file.txt', INJECTED);
|
||||
|
||||
Reference in new issue
Block a user