Skip to content

Commit 140594b

Browse files
authored
Reject absolute paths in devToolsFileFromPath (#9844)
* Reject absolute paths in devToolsFileFromPath * Add tests for devToolsFileFromPath path validation * Add release note for rejecting absolute paths in DevTools file reads * Use a non-sensitive example path in absolute-path rejection test
1 parent f52f72d commit 140594b

3 files changed

Lines changed: 53 additions & 3 deletions

File tree

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ To learn more about DevTools, check out the
1919
* Fixed a bug where highlighted search matches in tables were unreadable in dark
2020
mode because the highlight color had become fully opaque. -
2121
[#9863](https://github.com/flutter/devtools/pull/9863)
22+
* Rejected absolute paths in DevTools server file reads so they stay within
23+
the `~/.flutter-devtools/` directory and cannot resolve to arbitrary files
24+
on disk. -
25+
[#9844](https://github.com/flutter/devtools/pull/9844)
2226

2327
## Inspector updates
2428

packages/devtools_shared/lib/src/server/file_system.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,24 @@ extension LocalFileSystem on Never {
4545
///
4646
/// Only files within ~/.flutter-devtools/ can be accessed.
4747
static File? devToolsFileFromPath(String pathFromDevToolsDir) {
48-
if (pathFromDevToolsDir.contains('..')) {
48+
if (pathFromDevToolsDir.contains('..') ||
49+
path.isAbsolute(pathFromDevToolsDir)) {
4950
// The passed in path should not be able to walk up the directory tree
50-
// outside of the ~/.flutter-devtools/ directory.
51+
// outside of the ~/.flutter-devtools/ directory. It must also not be an
52+
// absolute path: path.join() discards the base directory when its second
53+
// argument is absolute, which would otherwise allow reading an arbitrary
54+
// file on disk (e.g. an absolute path to a credentials .json file).
5155
return null;
5256
}
5357

5458
ensureDevToolsDirectory();
55-
final file = File(path.join(devToolsDir(), pathFromDevToolsDir));
59+
final devToolsDirPath = devToolsDir();
60+
final file = File(path.join(devToolsDirPath, pathFromDevToolsDir));
61+
// Defense in depth: ensure the resolved path is actually contained within
62+
// the DevTools directory.
63+
if (!path.isWithin(devToolsDirPath, file.path)) {
64+
return null;
65+
}
5666
if (!file.existsSync()) {
5767
return null;
5868
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2026 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
4+
5+
import 'package:devtools_shared/src/server/file_system.dart';
6+
import 'package:test/test.dart';
7+
8+
void main() {
9+
group('LocalFileSystem.devToolsFileFromPath path validation', () {
10+
// These inputs must be rejected before any filesystem access so that reads
11+
// stay confined to the ~/.flutter-devtools/ directory.
12+
13+
test('rejects absolute paths', () {
14+
// path.join() discards the base directory when its second argument is
15+
// absolute, so an absolute path would otherwise escape the DevTools
16+
// directory and read an arbitrary file on disk.
17+
expect(LocalFileSystem.devToolsFileFromPath('/etc/passwd'), isNull);
18+
expect(
19+
LocalFileSystem.devToolsFileFromPath('/absolute/path/to/file.json'),
20+
isNull,
21+
);
22+
});
23+
24+
test('rejects paths containing ".."', () {
25+
expect(LocalFileSystem.devToolsFileFromPath('..'), isNull);
26+
expect(
27+
LocalFileSystem.devToolsFileFromPath('../../../etc/passwd'),
28+
isNull,
29+
);
30+
expect(
31+
LocalFileSystem.devToolsFileFromPath('subdir/../../escape.json'),
32+
isNull,
33+
);
34+
});
35+
});
36+
}

0 commit comments

Comments
 (0)