From 741e161c2964a922cc59dd3c204589c520e76f6a Mon Sep 17 00:00:00 2001 From: Mohamed Al-Kainai Date: Wed, 7 May 2025 16:01:35 +0200 Subject: [PATCH 1/3] Refactor filesystem permissions to normalize paths using 'package:path' for consistency --- lib/src/eval/runtime/runtime.dart | 7 +++++++ .../security/permissions/filesystem.dart | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/lib/src/eval/runtime/runtime.dart b/lib/src/eval/runtime/runtime.dart index 6f778801..3f64e8a6 100644 --- a/lib/src/eval/runtime/runtime.dart +++ b/lib/src/eval/runtime/runtime.dart @@ -3,6 +3,8 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; +import 'package:path/path.dart' as p; + import 'package:dart_eval/dart_eval.dart'; import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:dart_eval/dart_eval_security.dart'; @@ -310,6 +312,11 @@ class Runtime { /// Check if a permission is granted. bool checkPermission(String domain, [Object? data]) { + if (domain == 'filesystem:read' || domain == 'filesystem:write') { + if (data is String) { + data = p.normalize(p.absolute(data)); + } + } return _permissions[domain]?.any((element) => element.match(data)) ?? false; } diff --git a/lib/src/eval/runtime/security/permissions/filesystem.dart b/lib/src/eval/runtime/security/permissions/filesystem.dart index ae518c0c..a5e4d58a 100644 --- a/lib/src/eval/runtime/security/permissions/filesystem.dart +++ b/lib/src/eval/runtime/security/permissions/filesystem.dart @@ -1,3 +1,4 @@ +import 'package:path/path.dart' as p; import 'package:dart_eval/dart_eval_security.dart'; /// A permission that allows access to read and write a file system resource. @@ -14,13 +15,15 @@ class FilesystemPermission implements Permission { /// Create a new filesystem permission that matches any file in a directory /// or one of its subdirectories. factory FilesystemPermission.directory(String dir) { - final escaped = dir.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final normalized = p.normalize(p.absolute(dir)); + final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemPermission(RegExp('^$escaped.*')); } /// Create a new filesystem permission that matches a specific file. factory FilesystemPermission.file(String file) { - final escaped = file.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final normalized = p.normalize(p.absolute(file)); + final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemPermission(RegExp('^$escaped\$')); } @@ -59,13 +62,15 @@ class FilesystemReadPermission extends FilesystemPermission { /// Create a new filesystem permission that matches any file in a directory /// or one of its subdirectories. factory FilesystemReadPermission.directory(String dir) { - final escaped = dir.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final normalized = p.normalize(p.absolute(dir)); + final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemReadPermission(RegExp('^$escaped.*')); } /// Create a new filesystem permission that matches a specific file. factory FilesystemReadPermission.file(String file) { - final escaped = file.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final normalized = p.normalize(p.absolute(file)); + final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemReadPermission(RegExp('^$escaped\$')); } @@ -104,13 +109,15 @@ class FilesystemWritePermission extends FilesystemPermission { /// Create a new filesystem permission that matches any file in a directory /// or one of its subdirectories. factory FilesystemWritePermission.directory(String dir) { - final escaped = dir.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final normalized = p.normalize(p.absolute(dir)); + final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemWritePermission(RegExp('^$escaped.*')); } /// Create a new filesystem permission that matches a specific file. factory FilesystemWritePermission.file(String file) { - final escaped = file.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final normalized = p.normalize(p.absolute(file)); + final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemWritePermission(RegExp('^$escaped\$')); } From 0016afc7c8176609428fefe01fbf87043ebb6cba Mon Sep 17 00:00:00 2001 From: Mohamed Al-Kainai Date: Tue, 8 Jul 2025 05:11:44 +0200 Subject: [PATCH 2/3] Re: Fix filesystem path normalization for permission checks --- lib/src/eval/runtime/runtime.dart | 7 ------- .../eval/runtime/security/permissions/filesystem.dart | 9 ++++++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/src/eval/runtime/runtime.dart b/lib/src/eval/runtime/runtime.dart index 3f64e8a6..6f778801 100644 --- a/lib/src/eval/runtime/runtime.dart +++ b/lib/src/eval/runtime/runtime.dart @@ -3,8 +3,6 @@ import 'dart:convert'; import 'dart:math'; import 'dart:typed_data'; -import 'package:path/path.dart' as p; - import 'package:dart_eval/dart_eval.dart'; import 'package:dart_eval/dart_eval_bridge.dart'; import 'package:dart_eval/dart_eval_security.dart'; @@ -312,11 +310,6 @@ class Runtime { /// Check if a permission is granted. bool checkPermission(String domain, [Object? data]) { - if (domain == 'filesystem:read' || domain == 'filesystem:write') { - if (data is String) { - data = p.normalize(p.absolute(data)); - } - } return _permissions[domain]?.any((element) => element.match(data)) ?? false; } diff --git a/lib/src/eval/runtime/security/permissions/filesystem.dart b/lib/src/eval/runtime/security/permissions/filesystem.dart index a5e4d58a..03d946a4 100644 --- a/lib/src/eval/runtime/security/permissions/filesystem.dart +++ b/lib/src/eval/runtime/security/permissions/filesystem.dart @@ -33,7 +33,8 @@ class FilesystemPermission implements Permission { @override bool match([Object? data]) { if (data is String) { - return matchPattern.matchAsPrefix(data) != null; + final normalized = p.normalize(p.absolute(data)); + return matchPattern.matchAsPrefix(normalized) != null; } return false; } @@ -80,7 +81,8 @@ class FilesystemReadPermission extends FilesystemPermission { @override bool match([Object? data]) { if (data is String) { - return matchPattern.matchAsPrefix(data) != null; + final normalized = p.normalize(p.absolute(data)); + return matchPattern.matchAsPrefix(normalized) != null; } return false; } @@ -127,7 +129,8 @@ class FilesystemWritePermission extends FilesystemPermission { @override bool match([Object? data]) { if (data is String) { - return matchPattern.matchAsPrefix(data) != null; + final normalized = p.normalize(p.absolute(data)); + return matchPattern.matchAsPrefix(normalized) != null; } return false; } From d1a076951e4223c8cbaf238f29ddb00fbdcab262 Mon Sep 17 00:00:00 2001 From: Mohamed Al-Kainai Date: Wed, 9 Jul 2025 15:18:24 +0200 Subject: [PATCH 3/3] Fix filesystem permission path matching by resolving symbolic links Add resolveSymbolicLinksSync() to handle Android /data/data -> /data/user/0 symlink aliasing and ensure consistent path matching --- .../security/permissions/filesystem.dart | 64 +++++++++++++------ 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/lib/src/eval/runtime/security/permissions/filesystem.dart b/lib/src/eval/runtime/security/permissions/filesystem.dart index 03d946a4..01079740 100644 --- a/lib/src/eval/runtime/security/permissions/filesystem.dart +++ b/lib/src/eval/runtime/security/permissions/filesystem.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:path/path.dart' as p; import 'package:dart_eval/dart_eval_security.dart'; @@ -12,18 +13,41 @@ class FilesystemPermission implements Permission { /// A permission that allows access to any file system resource. static final FilesystemPermission any = FilesystemPermission(RegExp('.*')); + /// Resolves a path to its canonical form, handling symlinks and different + /// path representations (e.g., /data/data vs /data/user/0 on Android). + static String _resolvePath(String path) { + try { + final file = File(path); + if (file.existsSync()) { + return file.resolveSymbolicLinksSync(); + } + + final dir = Directory(p.dirname(path)); + if (dir.existsSync()) { + final resolvedParent = dir.resolveSymbolicLinksSync(); + return p.join(resolvedParent, p.basename(path)); + } + + return p.normalize(p.absolute(path)); + } catch (e) { + return p.normalize(p.absolute(path)); + } + } + /// Create a new filesystem permission that matches any file in a directory /// or one of its subdirectories. factory FilesystemPermission.directory(String dir) { - final normalized = p.normalize(p.absolute(dir)); - final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final resolvedPath = _resolvePath(dir); + final escaped = + resolvedPath.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemPermission(RegExp('^$escaped.*')); } /// Create a new filesystem permission that matches a specific file. factory FilesystemPermission.file(String file) { - final normalized = p.normalize(p.absolute(file)); - final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final resolvedPath = _resolvePath(file); + final escaped = + resolvedPath.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemPermission(RegExp('^$escaped\$')); } @@ -33,8 +57,8 @@ class FilesystemPermission implements Permission { @override bool match([Object? data]) { if (data is String) { - final normalized = p.normalize(p.absolute(data)); - return matchPattern.matchAsPrefix(normalized) != null; + final resolvedPath = _resolvePath(data); + return matchPattern.matchAsPrefix(resolvedPath) != null; } return false; } @@ -63,15 +87,17 @@ class FilesystemReadPermission extends FilesystemPermission { /// Create a new filesystem permission that matches any file in a directory /// or one of its subdirectories. factory FilesystemReadPermission.directory(String dir) { - final normalized = p.normalize(p.absolute(dir)); - final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final resolvedPath = FilesystemPermission._resolvePath(dir); + final escaped = + resolvedPath.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemReadPermission(RegExp('^$escaped.*')); } /// Create a new filesystem permission that matches a specific file. factory FilesystemReadPermission.file(String file) { - final normalized = p.normalize(p.absolute(file)); - final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final resolvedPath = FilesystemPermission._resolvePath(file); + final escaped = + resolvedPath.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemReadPermission(RegExp('^$escaped\$')); } @@ -81,8 +107,8 @@ class FilesystemReadPermission extends FilesystemPermission { @override bool match([Object? data]) { if (data is String) { - final normalized = p.normalize(p.absolute(data)); - return matchPattern.matchAsPrefix(normalized) != null; + final resolvedPath = FilesystemPermission._resolvePath(data); + return matchPattern.matchAsPrefix(resolvedPath) != null; } return false; } @@ -111,15 +137,17 @@ class FilesystemWritePermission extends FilesystemPermission { /// Create a new filesystem permission that matches any file in a directory /// or one of its subdirectories. factory FilesystemWritePermission.directory(String dir) { - final normalized = p.normalize(p.absolute(dir)); - final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final resolvedPath = FilesystemPermission._resolvePath(dir); + final escaped = + resolvedPath.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemWritePermission(RegExp('^$escaped.*')); } /// Create a new filesystem permission that matches a specific file. factory FilesystemWritePermission.file(String file) { - final normalized = p.normalize(p.absolute(file)); - final escaped = normalized.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); + final resolvedPath = FilesystemPermission._resolvePath(file); + final escaped = + resolvedPath.replaceAll(r'\', r'\\').replaceAll(r'/', r'\/'); return FilesystemWritePermission(RegExp('^$escaped\$')); } @@ -129,8 +157,8 @@ class FilesystemWritePermission extends FilesystemPermission { @override bool match([Object? data]) { if (data is String) { - final normalized = p.normalize(p.absolute(data)); - return matchPattern.matchAsPrefix(normalized) != null; + final resolvedPath = FilesystemPermission._resolvePath(data); + return matchPattern.matchAsPrefix(resolvedPath) != null; } return false; }