Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion lib/common/task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,16 @@ Future<MigrationData> _restoreTask(RootIsolateToken token) async {
final dir = Directory(restoreDirPath);
await dir.create(recursive: true);
for (final file in archive.files) {
final outPath = join(restoreDirPath, posix.normalize(file.name));
final outPath = canonicalize(join(restoreDirPath, file.name));
final canonicalRestoreDir = canonicalize(restoreDirPath);
if (!outPath.startsWith('$canonicalRestoreDir${Platform.pathSeparator}') &&
outPath != canonicalRestoreDir) {
throw 'Invalid zip entry: path traversal detected in "${file.name}"';
}
Comment on lines +536 to +541
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canonicalRestoreDir is recomputed for every archive entry, even though it’s constant for the whole restore. Compute it once before the loop (and consider using path.isWithin(canonicalRestoreDir, outPath) for the containment check) to avoid repeated canonicalization and reduce the chance of subtle separator/edge-case bugs in the string startsWith logic.

Copilot uses AI. Check for mistakes.
final parent = Directory(dirname(outPath));
if (!await parent.exists()) {
await parent.create(recursive: true);
}
final outputStream = OutputFileStream(outPath);
file.writeContent(outputStream);
await outputStream.close();
Comment on lines +542 to 548
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

archive.files may include directory entries (or an entry with name ./empty). In those cases OutputFileStream(outPath) will try to open a file where a directory should exist (or even the restore directory itself), which can fail or clobber expected directory structure. Handle file.isDirectory/!file.isFile explicitly (create the directory and continue) and reject/skip entries that resolve exactly to the restore root when a file is being written.

Copilot uses AI. Check for mistakes.
Expand Down
Loading