Launch browsers with redirecting files (#2703) The test runner creates a secret key used for communication with the browsers to prevent third party processes from connecting as if they were running test suites, but this key is current leaking through the command arguments when starting the browser process. We are already using temp directories which should prevent external reads and the safari browser launcher already used this pattern. Extend the `redirect.html` pattern to chromium and firefox browsers.
diff --git a/pkgs/test/CHANGELOG.md b/pkgs/test/CHANGELOG.md index fa768d3..8df1963 100644 --- a/pkgs/test/CHANGELOG.md +++ b/pkgs/test/CHANGELOG.md
@@ -9,6 +9,8 @@ in `GithubReporter`. * Only include VM debugger in output when using interactive debugging, suppress it when only using coverage. +* Use a redirect html file for browser tests to avoid leaking websocket details + through process starting arguments. ## 1.31.2
diff --git a/pkgs/test/lib/src/runner/browser/chrome.dart b/pkgs/test/lib/src/runner/browser/chrome.dart index f23d553..43fdcb3 100644 --- a/pkgs/test/lib/src/runner/browser/chrome.dart +++ b/pkgs/test/lib/src/runner/browser/chrome.dart
@@ -170,8 +170,8 @@ var tabs = await chromeConnection.getTabs(); tab = tabs.firstWhereOrNull((tab) => tab.url == url.toString()); if (tab == null) { - await Future<void>.delayed(const Duration(milliseconds: 100)); - if (attempt > 5) { + await Future<void>.delayed(const Duration(milliseconds: 200)); + if (attempt > 20) { throw StateError('Could not connect to test tab with url: $url'); } }
diff --git a/pkgs/test/lib/src/runner/browser/chromium.dart b/pkgs/test/lib/src/runner/browser/chromium.dart index 4ac6835..f3d1244 100644 --- a/pkgs/test/lib/src/runner/browser/chromium.dart +++ b/pkgs/test/lib/src/runner/browser/chromium.dart
@@ -3,8 +3,10 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; +import 'package:path/path.dart' as p; import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_imports import 'package:test_core/src/runner/configuration.dart'; // ignore: implementation_imports import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports @@ -29,9 +31,14 @@ settings ??= defaultSettings[runtime]; var dir = createTempDir(); + var redirect = p.join(dir, 'redirect.html'); + File(redirect).writeAsStringSync( + '<script>location = ${jsonEncode(url.toString())}</script>', + ); + var args = [ '--user-data-dir=$dir', - url.toString(), + redirect, '--enable-logging=stderr', '--v=0', '--disable-extensions',
diff --git a/pkgs/test/lib/src/runner/browser/firefox.dart b/pkgs/test/lib/src/runner/browser/firefox.dart index e67fb67..384edc2 100644 --- a/pkgs/test/lib/src/runner/browser/firefox.dart +++ b/pkgs/test/lib/src/runner/browser/firefox.dart
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as p; @@ -44,9 +45,14 @@ var dir = createTempDir(); File(p.join(dir, 'prefs.js')).writeAsStringSync(_preferences); + var redirect = p.join(dir, 'redirect.html'); + File(redirect).writeAsStringSync( + '<script>location = ${jsonEncode(url.toString())}</script>', + ); + var process = await Process.start( settings.executable, - ['--profile', dir, url.toString(), '--no-remote', ...settings.arguments], + ['--profile', dir, redirect, '--no-remote', ...settings.arguments], environment: {'MOZ_CRASHREPORTER_DISABLE': '1', 'MOZ_AUTOMATION': '1'}, );
diff --git a/pkgs/test/test/runner/browser/chrome_test.dart b/pkgs/test/test/runner/browser/chrome_test.dart index 8663d2e..e2142a8 100644 --- a/pkgs/test/test/runner/browser/chrome_test.dart +++ b/pkgs/test/test/runner/browser/chrome_test.dart
@@ -6,6 +6,9 @@ @Tags(['chrome']) library; +import 'dart:io'; + +import 'package:path/path.dart' as p; import 'package:test/src/runner/browser/chrome.dart'; import 'package:test/src/runner/executable_settings.dart'; import 'package:test/test.dart'; @@ -110,4 +113,30 @@ expect(test.stdout, emitsThrough(contains('Failed to run Chrome:'))); await test.shouldExit(1); }); + + test('does not pass target URL directly in command line arguments', () async { + var targetUrl = Uri.parse('http://localhost:12345/secret_token_12345'); + var argsFile = p.join(d.sandbox, 'args.txt'); + var scriptFile = p.join(d.sandbox, 'fake_chrome.sh'); + await d.file('fake_chrome.sh', ''' +#!/bin/sh +echo "\$@" > "$argsFile" +''').create(); + await Process.run('chmod', ['+x', scriptFile]); + + var chrome = Chrome( + targetUrl, + configuration(), + settings: ExecutableSettings( + linuxExecutable: scriptFile, + macOSExecutable: scriptFile, + windowsExecutable: scriptFile, + ), + ); + await chrome.onExit.catchError((_) {}); + + var argsText = await File(argsFile).readAsString(); + expect(argsText, isNot(contains('secret_token_12345'))); + expect(argsText, contains('redirect.html')); + }, testOn: '!windows'); }
diff --git a/pkgs/test/test/runner/browser/firefox_test.dart b/pkgs/test/test/runner/browser/firefox_test.dart index cf89f9e..581502a 100644 --- a/pkgs/test/test/runner/browser/firefox_test.dart +++ b/pkgs/test/test/runner/browser/firefox_test.dart
@@ -5,6 +5,9 @@ @Tags(['firefox']) library; +import 'dart:io'; + +import 'package:path/path.dart' as p; import 'package:test/src/runner/browser/firefox.dart'; import 'package:test/src/runner/executable_settings.dart'; import 'package:test/test.dart'; @@ -123,4 +126,29 @@ expect(test.stdout, emitsThrough(contains('+1: All tests passed!'))); await test.shouldExit(0); }); + + test('does not pass target URL directly in command line arguments', () async { + var targetUrl = Uri.parse('http://localhost:12345/secret_token_12345'); + var argsFile = p.join(d.sandbox, 'args.txt'); + var scriptFile = p.join(d.sandbox, 'fake_firefox.sh'); + await d.file('fake_firefox.sh', ''' +#!/bin/sh +echo "\$@" > "$argsFile" +''').create(); + await Process.run('chmod', ['+x', scriptFile]); + + var firefox = Firefox( + targetUrl, + settings: ExecutableSettings( + linuxExecutable: scriptFile, + macOSExecutable: scriptFile, + windowsExecutable: scriptFile, + ), + ); + await firefox.onExit.catchError((_) {}); + + var argsText = await File(argsFile).readAsString(); + expect(argsText, isNot(contains('secret_token_12345'))); + expect(argsText, contains('redirect.html')); + }, testOn: '!windows'); }