Skip to content

Commit 3282c23

Browse files
authored
Disable sandboxing for macOS tests in CI (#6866)
macOS 14 added new requirements that un-codesigned sandbox apps must be granted access when changed. Waiting for this UI caused macOS tests to fail on macOS 14. Additionally, adding codesigning is not sufficient, since it must still be approved before codesigning is enough to pass the check. As a workaround, this PR disables sandboxing for macOS tests in CI. ![Screenshot 2024-05-30 at 2 41 33�PM](https://github.com/flutter/flutter/assets/682784/1bc32620-5edb-420a-866c-5cc529b2ac55) https://developer.apple.com/documentation/updates/security#June-2023) > App Sandbox now associates your macOS app with its sandbox container using its code signature. The operating system asks the person using your app to grant permission if it tries to access a sandbox container associated with a different app. For more information, see [Accessing files from the macOS App Sandbox](https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox). And that link explains why this is happening on a macOS 14 update: > In macOS 14 and later, the operating system uses your app�s code signature to associate it with its sandbox container. If your app tries to access the sandbox container owned by another app, the system asks the person using your app whether to grant access. If the person denies access and your app is already running, then it can�t read or write the files in the other app�s sandbox container. If the person denies access while your app is launching and trying to enter the other app�s sandbox container, your app fails to launch. > > The operating system also tracks the association between an app�s code signing identity and its sandbox container for helper tools, including launch agents. If a person denies permission for a launch agent to enter its sandbox container and the app fails to start, launchd starts the launch agent again and the operating system re-requests access. Fixes packages part of flutter/flutter#149264. Verified tests pass: https://ci.chromium.org/ui/p/flutter/builders/staging.shadow/Mac_arm64%20macos_platform_tests%20master%20-%20packages/6/overview
1 parent 586faa6 commit 3282c23

File tree

6 files changed

+100
-5
lines changed

6 files changed

+100
-5
lines changed

packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>com.apple.security.app-sandbox</key>
6-
<true/>
6+
<false/>
77
<key>com.apple.security.cs.allow-jit</key>
88
<true/>
99
<key>com.apple.security.network.server</key>

packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
<plist version="1.0">
44
<dict>
55
<key>com.apple.security.app-sandbox</key>
6-
<true/>
6+
<false/>
77
</dict>
88
</plist>

script/tool/lib/src/common/xcode.dart

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,37 @@ class Xcode {
3232

3333
/// Runs an `xcodebuild` in [directory] with the given parameters.
3434
Future<int> runXcodeBuild(
35-
Directory directory, {
35+
Directory exampleDirectory,
36+
String platform, {
3637
List<String> actions = const <String>['build'],
3738
required String workspace,
3839
required String scheme,
3940
String? configuration,
4041
List<String> extraFlags = const <String>[],
4142
}) {
43+
File? disabledSandboxEntitlementFile;
44+
if (actions.contains('test') && platform.toLowerCase() == 'macos') {
45+
disabledSandboxEntitlementFile = _createDisabledSandboxEntitlementFile(
46+
exampleDirectory.childDirectory(platform.toLowerCase()),
47+
configuration ?? 'Debug',
48+
);
49+
}
4250
final List<String> args = <String>[
4351
_xcodeBuildCommand,
4452
...actions,
4553
...<String>['-workspace', workspace],
4654
...<String>['-scheme', scheme],
4755
if (configuration != null) ...<String>['-configuration', configuration],
4856
...extraFlags,
57+
if (disabledSandboxEntitlementFile != null)
58+
'CODE_SIGN_ENTITLEMENTS=${disabledSandboxEntitlementFile.path}',
4959
];
5060
final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}';
5161
if (log) {
5262
print(completeTestCommand);
5363
}
5464
return processRunner.runAndStream(_xcRunCommand, args,
55-
workingDir: directory);
65+
workingDir: exampleDirectory);
5666
}
5767

5868
/// Returns true if [project], which should be an .xcodeproj directory,
@@ -156,4 +166,48 @@ class Xcode {
156166
}
157167
return null;
158168
}
169+
170+
/// Finds and copies macOS entitlements file. In the copy, disables sandboxing.
171+
/// If entitlements file is not found, returns null.
172+
///
173+
/// As of macOS 14, testing a macOS sandbox app may prompt the user to grant
174+
/// access to the app. To workaround this in CI, we create and use a entitlements
175+
/// file with sandboxing disabled. See
176+
/// https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox.
177+
File? _createDisabledSandboxEntitlementFile(
178+
Directory macOSDirectory,
179+
String configuration,
180+
) {
181+
final String entitlementDefaultFileName =
182+
configuration == 'Release' ? 'Release' : 'DebugProfile';
183+
184+
final File entitlementFile = macOSDirectory
185+
.childDirectory('Runner')
186+
.childFile('$entitlementDefaultFileName.entitlements');
187+
188+
if (!entitlementFile.existsSync()) {
189+
print('Unable to find entitlements file at ${entitlementFile.path}');
190+
return null;
191+
}
192+
193+
final String originalEntitlementFileContents =
194+
entitlementFile.readAsStringSync();
195+
final File disabledSandboxEntitlementFile = macOSDirectory
196+
.fileSystem.systemTempDirectory
197+
.createTempSync('flutter_disable_sandbox_entitlement.')
198+
.childFile(
199+
'${entitlementDefaultFileName}WithDisabledSandboxing.entitlements');
200+
disabledSandboxEntitlementFile.createSync(recursive: true);
201+
disabledSandboxEntitlementFile.writeAsStringSync(
202+
originalEntitlementFileContents.replaceAll(
203+
RegExp(
204+
r'<key>com\.apple\.security\.app-sandbox<\/key>[\S\s]*?<true\/>'),
205+
'''
206+
<key>com.apple.security.app-sandbox</key>
207+
<false/>''',
208+
),
209+
);
210+
211+
return disabledSandboxEntitlementFile;
212+
}
159213
}

script/tool/lib/src/native_test_command.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ this command.
465465
_printRunningExampleTestsMessage(example, platform);
466466
final int exitCode = await _xcode.runXcodeBuild(
467467
example.directory,
468+
platform,
468469
// Clean before testing to remove cached swiftmodules from previous
469470
// runs, which can cause conflicts.
470471
actions: <String>['clean', 'test'],

script/tool/lib/src/xcode_analyze_command.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand {
111111
print('Running $platform tests and analyzer for $examplePath...');
112112
final int exitCode = await _xcode.runXcodeBuild(
113113
example.directory,
114+
platform,
114115
// Clean before analyzing to remove cached swiftmodules from previous
115116
// runs, which can cause conflicts.
116117
actions: <String>['clean', 'analyze'],

script/tool/test/common/xcode_test.dart

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:convert';
66

77
import 'package:file/file.dart';
88
import 'package:file/local.dart';
9+
import 'package:file/memory.dart';
910
import 'package:flutter_plugin_tools/src/common/xcode.dart';
1011
import 'package:test/test.dart';
1112

@@ -161,6 +162,7 @@ void main() {
161162

162163
final int exitCode = await xcode.runXcodeBuild(
163164
directory,
165+
'ios',
164166
workspace: 'A.xcworkspace',
165167
scheme: 'AScheme',
166168
);
@@ -186,7 +188,7 @@ void main() {
186188
test('handles all arguments', () async {
187189
final Directory directory = const LocalFileSystem().currentDirectory;
188190

189-
final int exitCode = await xcode.runXcodeBuild(directory,
191+
final int exitCode = await xcode.runXcodeBuild(directory, 'ios',
190192
actions: <String>['action1', 'action2'],
191193
workspace: 'A.xcworkspace',
192194
scheme: 'AScheme',
@@ -225,6 +227,7 @@ void main() {
225227

226228
final int exitCode = await xcode.runXcodeBuild(
227229
directory,
230+
'ios',
228231
workspace: 'A.xcworkspace',
229232
scheme: 'AScheme',
230233
);
@@ -246,6 +249,42 @@ void main() {
246249
directory.path),
247250
]));
248251
});
252+
253+
test('sets CODE_SIGN_ENTITLEMENTS for macos tests', () async {
254+
final FileSystem fileSystem = MemoryFileSystem();
255+
final Directory directory = fileSystem.currentDirectory;
256+
directory
257+
.childDirectory('macos')
258+
.childDirectory('Runner')
259+
.childFile('DebugProfile.entitlements')
260+
.createSync(recursive: true);
261+
262+
final int exitCode = await xcode.runXcodeBuild(
263+
directory,
264+
'macos',
265+
workspace: 'A.xcworkspace',
266+
scheme: 'AScheme',
267+
actions: <String>['test'],
268+
);
269+
270+
expect(exitCode, 0);
271+
expect(
272+
processRunner.recordedCalls,
273+
orderedEquals(<ProcessCall>[
274+
ProcessCall(
275+
'xcrun',
276+
const <String>[
277+
'xcodebuild',
278+
'test',
279+
'-workspace',
280+
'A.xcworkspace',
281+
'-scheme',
282+
'AScheme',
283+
'CODE_SIGN_ENTITLEMENTS=/.tmp_rand0/flutter_disable_sandbox_entitlement.rand0/DebugProfileWithDisabledSandboxing.entitlements'
284+
],
285+
directory.path),
286+
]));
287+
});
249288
});
250289

251290
group('projectHasTarget', () {

0 commit comments

Comments
 (0)