From b6dec84fea87a4a518ecdfd7344c83f53ca03278 Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Mon, 1 Jun 2026 13:26:51 +0300 Subject: [PATCH 1/4] Reject absolute paths in devToolsFileFromPath --- .../lib/src/server/file_system.dart | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/devtools_shared/lib/src/server/file_system.dart b/packages/devtools_shared/lib/src/server/file_system.dart index 58dc6bcf281..1f79c0fbdb4 100644 --- a/packages/devtools_shared/lib/src/server/file_system.dart +++ b/packages/devtools_shared/lib/src/server/file_system.dart @@ -45,14 +45,24 @@ extension LocalFileSystem on Never { /// /// Only files within ~/.flutter-devtools/ can be accessed. static File? devToolsFileFromPath(String pathFromDevToolsDir) { - if (pathFromDevToolsDir.contains('..')) { + if (pathFromDevToolsDir.contains('..') || + path.isAbsolute(pathFromDevToolsDir)) { // The passed in path should not be able to walk up the directory tree - // outside of the ~/.flutter-devtools/ directory. + // outside of the ~/.flutter-devtools/ directory. It must also not be an + // absolute path: path.join() discards the base directory when its second + // argument is absolute, which would otherwise allow reading an arbitrary + // file on disk (e.g. an absolute path to a credentials .json file). return null; } ensureDevToolsDirectory(); - final file = File(path.join(devToolsDir(), pathFromDevToolsDir)); + final devToolsDirPath = devToolsDir(); + final file = File(path.join(devToolsDirPath, pathFromDevToolsDir)); + // Defense in depth: ensure the resolved path is actually contained within + // the DevTools directory. + if (!path.isWithin(devToolsDirPath, file.path)) { + return null; + } if (!file.existsSync()) { return null; } From 39b5fe5a823f71a3df049a6d449258c1252094ba Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Mon, 1 Jun 2026 13:32:25 +0300 Subject: [PATCH 2/4] Add tests for devToolsFileFromPath path validation --- .../test/server/file_system_test.dart | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 packages/devtools_shared/test/server/file_system_test.dart diff --git a/packages/devtools_shared/test/server/file_system_test.dart b/packages/devtools_shared/test/server/file_system_test.dart new file mode 100644 index 00000000000..8baeb6a3513 --- /dev/null +++ b/packages/devtools_shared/test/server/file_system_test.dart @@ -0,0 +1,38 @@ +// Copyright 2026 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'package:devtools_shared/src/server/file_system.dart'; +import 'package:test/test.dart'; + +void main() { + group('LocalFileSystem.devToolsFileFromPath path validation', () { + // These inputs must be rejected before any filesystem access so that reads + // stay confined to the ~/.flutter-devtools/ directory. + + test('rejects absolute paths', () { + // path.join() discards the base directory when its second argument is + // absolute, so an absolute path would otherwise escape the DevTools + // directory and read an arbitrary file on disk. + expect(LocalFileSystem.devToolsFileFromPath('/etc/passwd'), isNull); + expect( + LocalFileSystem.devToolsFileFromPath( + '/home/user/.config/gcloud/application_default_credentials.json', + ), + isNull, + ); + }); + + test('rejects paths containing ".."', () { + expect(LocalFileSystem.devToolsFileFromPath('..'), isNull); + expect( + LocalFileSystem.devToolsFileFromPath('../../../etc/passwd'), + isNull, + ); + expect( + LocalFileSystem.devToolsFileFromPath('subdir/../../escape.json'), + isNull, + ); + }); + }); +} From 19b28414b153541871fcf35416991b5ac7205c1b Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Mon, 22 Jun 2026 23:25:32 +0300 Subject: [PATCH 3/4] Add release note for rejecting absolute paths in DevTools file reads --- packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md index 2cd2c928fe0..de0173466f3 100644 --- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md +++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md @@ -15,7 +15,10 @@ To learn more about DevTools, check out the ## General updates -TODO: Remove this section if there are not any updates. +* Rejected absolute paths in DevTools server file reads so they stay within + the `~/.flutter-devtools/` directory and cannot resolve to arbitrary files + on disk. - + [#9844](https://github.com/flutter/devtools/pull/9844) ## Inspector updates From 38631443aaef0d5282b29ee5cdba86fc3640295b Mon Sep 17 00:00:00 2001 From: adilburaksen Date: Tue, 23 Jun 2026 01:30:41 +0300 Subject: [PATCH 4/4] Use a non-sensitive example path in absolute-path rejection test --- packages/devtools_shared/test/server/file_system_test.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/devtools_shared/test/server/file_system_test.dart b/packages/devtools_shared/test/server/file_system_test.dart index 8baeb6a3513..78069ec1733 100644 --- a/packages/devtools_shared/test/server/file_system_test.dart +++ b/packages/devtools_shared/test/server/file_system_test.dart @@ -16,9 +16,7 @@ void main() { // directory and read an arbitrary file on disk. expect(LocalFileSystem.devToolsFileFromPath('/etc/passwd'), isNull); expect( - LocalFileSystem.devToolsFileFromPath( - '/home/user/.config/gcloud/application_default_credentials.json', - ), + LocalFileSystem.devToolsFileFromPath('/absolute/path/to/file.json'), isNull, ); });