@machina.ai/cell-cli 1.59.0-rc2 → 1.64.0-rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +11 -14
- package/dist/index.js.map +1 -1
- package/dist/package.json +3 -3
- package/dist/src/acp/acpResume.test.js +50 -0
- package/dist/src/acp/acpResume.test.js.map +1 -1
- package/dist/src/acp/acpRpcDispatcher.d.ts +1 -1
- package/dist/src/acp/acpRpcDispatcher.js +2 -2
- package/dist/src/acp/acpRpcDispatcher.js.map +1 -1
- package/dist/src/acp/acpRpcDispatcher.test.js +9 -0
- package/dist/src/acp/acpRpcDispatcher.test.js.map +1 -1
- package/dist/src/acp/acpSession.d.ts +1 -1
- package/dist/src/acp/acpSession.js +90 -80
- package/dist/src/acp/acpSession.js.map +1 -1
- package/dist/src/acp/acpSession.test.js +279 -2
- package/dist/src/acp/acpSession.test.js.map +1 -1
- package/dist/src/acp/acpSessionManager.d.ts +2 -2
- package/dist/src/acp/acpSessionManager.js +165 -73
- package/dist/src/acp/acpSessionManager.js.map +1 -1
- package/dist/src/acp/acpSessionManager.test.js +204 -1
- package/dist/src/acp/acpSessionManager.test.js.map +1 -1
- package/dist/src/acp/acpUtils.js +5 -3
- package/dist/src/acp/acpUtils.js.map +1 -1
- package/dist/src/commands/extensions/validate.js +7 -3
- package/dist/src/commands/extensions/validate.js.map +1 -1
- package/dist/src/commands/extensions/validate.test.js +51 -0
- package/dist/src/commands/extensions/validate.test.js.map +1 -1
- package/dist/src/config/config.js +3 -0
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +6 -1
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/extension-manager.d.ts +25 -0
- package/dist/src/config/extension-manager.js +96 -15
- package/dist/src/config/extension-manager.js.map +1 -1
- package/dist/src/config/extension-manager.test.js +263 -1
- package/dist/src/config/extension-manager.test.js.map +1 -1
- package/dist/src/config/extension.test.js +10 -7
- package/dist/src/config/extension.test.js.map +1 -1
- package/dist/src/config/extensionRegistryClient.js +1 -1
- package/dist/src/config/extensionRegistryClient.js.map +1 -1
- package/dist/src/config/extensions/consent.js +98 -1
- package/dist/src/config/extensions/consent.js.map +1 -1
- package/dist/src/config/extensions/consent.test.js +89 -0
- package/dist/src/config/extensions/consent.test.js.map +1 -1
- package/dist/src/config/extensions/update.js +7 -2
- package/dist/src/config/extensions/update.js.map +1 -1
- package/dist/src/config/extensions/update.test.js +21 -0
- package/dist/src/config/extensions/update.test.js.map +1 -1
- package/dist/src/config/mcp/mcpServerEnablement.d.ts +5 -0
- package/dist/src/config/mcp/mcpServerEnablement.js +26 -3
- package/dist/src/config/mcp/mcpServerEnablement.js.map +1 -1
- package/dist/src/config/mcp/mcpServerEnablement.test.js +27 -0
- package/dist/src/config/mcp/mcpServerEnablement.test.js.map +1 -1
- package/dist/src/config/settings.js +20 -4
- package/dist/src/config/settings.js.map +1 -1
- package/dist/src/config/settings.test.js +121 -0
- package/dist/src/config/settings.test.js.map +1 -1
- package/dist/src/config/settingsSchema.d.ts +27 -0
- package/dist/src/config/settingsSchema.js +55 -1
- package/dist/src/config/settingsSchema.js.map +1 -1
- package/dist/src/core/auth.test.js +1 -1
- package/dist/src/core/auth.test.js.map +1 -1
- package/dist/src/gemini.js +110 -18
- package/dist/src/gemini.js.map +1 -1
- package/dist/src/gemini_uncaught.test.d.ts +6 -0
- package/dist/src/gemini_uncaught.test.js +91 -0
- package/dist/src/gemini_uncaught.test.js.map +1 -0
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/patches/http-proxy-agent.d.ts +4 -1
- package/dist/src/patches/http-proxy-agent.js +38 -2
- package/dist/src/patches/http-proxy-agent.js.map +1 -1
- package/dist/src/patches/https-proxy-agent.d.ts +4 -1
- package/dist/src/patches/https-proxy-agent.js +38 -2
- package/dist/src/patches/https-proxy-agent.js.map +1 -1
- package/dist/src/services/FeedbackService.integration.test.d.ts +6 -0
- package/dist/src/services/FeedbackService.integration.test.js +212 -0
- package/dist/src/services/FeedbackService.integration.test.js.map +1 -0
- package/dist/src/services/FeedbackService.js +62 -6
- package/dist/src/services/FeedbackService.js.map +1 -1
- package/dist/src/services/FeedbackService.test.d.ts +6 -0
- package/dist/src/services/FeedbackService.test.js +282 -0
- package/dist/src/services/FeedbackService.test.js.map +1 -0
- package/dist/src/services/FileCommandLoader.js +3 -5
- package/dist/src/services/FileCommandLoader.js.map +1 -1
- package/dist/src/services/FileCommandLoader.test.js +68 -6
- package/dist/src/services/FileCommandLoader.test.js.map +1 -1
- package/dist/src/test-utils/createExtension.d.ts +1 -1
- package/dist/src/test-utils/createExtension.js +15 -1
- package/dist/src/test-utils/createExtension.js.map +1 -1
- package/dist/src/ui/AppContainer.js +42 -39
- package/dist/src/ui/AppContainer.js.map +1 -1
- package/dist/src/ui/AppContainer.test.js +30 -0
- package/dist/src/ui/AppContainer.test.js.map +1 -1
- package/dist/src/ui/auth/AuthDialog.js +11 -3
- package/dist/src/ui/auth/AuthDialog.js.map +1 -1
- package/dist/src/ui/auth/useAuth.test.js +1 -1
- package/dist/src/ui/auth/useAuth.test.js.map +1 -1
- package/dist/src/ui/commands/bugCommand.js +3 -3
- package/dist/src/ui/commands/bugCommand.js.map +1 -1
- package/dist/src/ui/commands/bugCommand.test.js +44 -0
- package/dist/src/ui/commands/bugCommand.test.js.map +1 -1
- package/dist/src/ui/commands/bugMemoryCommand.js +2 -2
- package/dist/src/ui/commands/bugMemoryCommand.js.map +1 -1
- package/dist/src/ui/commands/bugMemoryCommand.test.js +12 -0
- package/dist/src/ui/commands/bugMemoryCommand.test.js.map +1 -1
- package/dist/src/ui/commands/statsCommand.js +17 -2
- package/dist/src/ui/commands/statsCommand.js.map +1 -1
- package/dist/src/ui/commands/statsCommand.test.js +2 -0
- package/dist/src/ui/commands/statsCommand.test.js.map +1 -1
- package/dist/src/ui/components/AboutBox.js +1 -1
- package/dist/src/ui/components/AboutBox.js.map +1 -1
- package/dist/src/ui/components/AsciiArt.d.ts +6 -6
- package/dist/src/ui/components/AsciiArt.js +6 -6
- package/dist/src/ui/components/AskUserDialog.js +7 -2
- package/dist/src/ui/components/AskUserDialog.js.map +1 -1
- package/dist/src/ui/components/AskUserDialog.test.js +33 -0
- package/dist/src/ui/components/AskUserDialog.test.js.map +1 -1
- package/dist/src/ui/components/Composer.js +4 -4
- package/dist/src/ui/components/Composer.js.map +1 -1
- package/dist/src/ui/components/Composer.test.js +50 -2
- package/dist/src/ui/components/Composer.test.js.map +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
- package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.js +16 -10
- package/dist/src/ui/components/InputPrompt.js.map +1 -1
- package/dist/src/ui/components/InputPrompt.test.js +103 -7
- package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
- package/dist/src/ui/components/LoadingIndicator.d.ts +1 -0
- package/dist/src/ui/components/LoadingIndicator.js +13 -11
- package/dist/src/ui/components/LoadingIndicator.js.map +1 -1
- package/dist/src/ui/components/LoadingIndicator.test.js +29 -0
- package/dist/src/ui/components/LoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/components/MainContent.js +79 -14
- package/dist/src/ui/components/MainContent.js.map +1 -1
- package/dist/src/ui/components/MainContent.test.js +218 -5
- package/dist/src/ui/components/MainContent.test.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.js +11 -13
- package/dist/src/ui/components/ModelDialog.js.map +1 -1
- package/dist/src/ui/components/ModelDialog.test.js +28 -24
- package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
- package/dist/src/ui/components/ModelStatsDisplay.test.js +2 -2
- package/dist/src/ui/components/ProgressBar.js +4 -3
- package/dist/src/ui/components/ProgressBar.js.map +1 -1
- package/dist/src/ui/components/ProgressBar.test.js +12 -0
- package/dist/src/ui/components/ProgressBar.test.js.map +1 -1
- package/dist/src/ui/components/RewindViewer.js +2 -1
- package/dist/src/ui/components/RewindViewer.js.map +1 -1
- package/dist/src/ui/components/RewindViewer.test.js +38 -0
- package/dist/src/ui/components/RewindViewer.test.js.map +1 -1
- package/dist/src/ui/components/SessionSummaryDisplay.js +28 -2
- package/dist/src/ui/components/SessionSummaryDisplay.js.map +1 -1
- package/dist/src/ui/components/SessionSummaryDisplay.test.js +41 -2
- package/dist/src/ui/components/SessionSummaryDisplay.test.js.map +1 -1
- package/dist/src/ui/components/StatsDisplay.d.ts +2 -1
- package/dist/src/ui/components/StatsDisplay.js +66 -4
- package/dist/src/ui/components/StatsDisplay.js.map +1 -1
- package/dist/src/ui/components/StatsDisplay.test.js +120 -3
- package/dist/src/ui/components/StatsDisplay.test.js.map +1 -1
- package/dist/src/ui/components/StatusRow.d.ts +2 -0
- package/dist/src/ui/components/StatusRow.js +11 -6
- package/dist/src/ui/components/StatusRow.js.map +1 -1
- package/dist/src/ui/components/StatusRow.test.js +158 -0
- package/dist/src/ui/components/StatusRow.test.js.map +1 -1
- package/dist/src/ui/components/ToolConfirmationQueue.js +4 -2
- package/dist/src/ui/components/ToolConfirmationQueue.js.map +1 -1
- package/dist/src/ui/components/ToolConfirmationQueue.test.js +30 -0
- package/dist/src/ui/components/ToolConfirmationQueue.test.js.map +1 -1
- package/dist/src/ui/components/messages/DenseToolMessage.js +5 -5
- package/dist/src/ui/components/messages/DenseToolMessage.js.map +1 -1
- package/dist/src/ui/components/messages/DenseToolMessage.test.js +13 -0
- package/dist/src/ui/components/messages/DenseToolMessage.test.js.map +1 -1
- package/dist/src/ui/components/messages/DiffRenderer.d.ts +1 -0
- package/dist/src/ui/components/messages/DiffRenderer.js +16 -8
- package/dist/src/ui/components/messages/DiffRenderer.js.map +1 -1
- package/dist/src/ui/components/messages/DiffRenderer.test.js +42 -0
- package/dist/src/ui/components/messages/DiffRenderer.test.js.map +1 -1
- package/dist/src/ui/components/messages/ShellToolMessage.js +4 -3
- package/dist/src/ui/components/messages/ShellToolMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js +74 -16
- package/dist/src/ui/components/messages/ToolConfirmationMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js +80 -7
- package/dist/src/ui/components/messages/ToolConfirmationMessage.test.js.map +1 -1
- package/dist/src/ui/components/messages/ToolGroupMessage.js +6 -5
- package/dist/src/ui/components/messages/ToolGroupMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolMessage.js +2 -1
- package/dist/src/ui/components/messages/ToolMessage.js.map +1 -1
- package/dist/src/ui/components/messages/ToolResultDisplay.js +11 -8
- package/dist/src/ui/components/messages/ToolResultDisplay.js.map +1 -1
- package/dist/src/ui/components/messages/ToolShared.js +5 -4
- package/dist/src/ui/components/messages/ToolShared.js.map +1 -1
- package/dist/src/ui/components/shared/HalfLinePaddedBox.js +4 -2
- package/dist/src/ui/components/shared/HalfLinePaddedBox.js.map +1 -1
- package/dist/src/ui/components/shared/HalfLinePaddedBox.test.js +8 -0
- package/dist/src/ui/components/shared/HalfLinePaddedBox.test.js.map +1 -1
- package/dist/src/ui/components/shared/SearchableList.js +1 -1
- package/dist/src/ui/components/shared/SearchableList.js.map +1 -1
- package/dist/src/ui/components/shared/SearchableList.test.js +13 -0
- package/dist/src/ui/components/shared/SearchableList.test.js.map +1 -1
- package/dist/src/ui/components/shared/TextInput.js +2 -2
- package/dist/src/ui/components/shared/TextInput.js.map +1 -1
- package/dist/src/ui/components/views/ExtensionRegistryView.test.js +2 -0
- package/dist/src/ui/components/views/ExtensionRegistryView.test.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.js +20 -2
- package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
- package/dist/src/ui/contexts/KeypressContext.test.js +24 -0
- package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
- package/dist/src/ui/contexts/SessionContext.js +12 -0
- package/dist/src/ui/contexts/SessionContext.js.map +1 -1
- package/dist/src/ui/contexts/SessionContext.test.js +5 -0
- package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
- package/dist/src/ui/contexts/UIStateContext.d.ts +1 -0
- package/dist/src/ui/contexts/UIStateContext.js.map +1 -1
- package/dist/src/ui/emergency-stop.test.d.ts +6 -0
- package/dist/src/ui/emergency-stop.test.js +116 -0
- package/dist/src/ui/emergency-stop.test.js.map +1 -0
- package/dist/src/ui/hooks/atCommandProcessor.d.ts +8 -5
- package/dist/src/ui/hooks/atCommandProcessor.js +47 -16
- package/dist/src/ui/hooks/atCommandProcessor.js.map +1 -1
- package/dist/src/ui/hooks/atCommandProcessor.test.js +160 -0
- package/dist/src/ui/hooks/atCommandProcessor.test.js.map +1 -1
- package/dist/src/ui/hooks/useComposerStatus.d.ts +1 -0
- package/dist/src/ui/hooks/useComposerStatus.js +1 -0
- package/dist/src/ui/hooks/useComposerStatus.js.map +1 -1
- package/dist/src/ui/hooks/useFocus.js +1 -0
- package/dist/src/ui/hooks/useFocus.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.js +16 -16
- package/dist/src/ui/hooks/useFolderTrust.js.map +1 -1
- package/dist/src/ui/hooks/useFolderTrust.test.js +186 -3
- package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.js +21 -15
- package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
- package/dist/src/ui/hooks/useGeminiStream.test.js +141 -3
- package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.d.ts +1 -0
- package/dist/src/ui/hooks/useLoadingIndicator.js +1 -0
- package/dist/src/ui/hooks/useLoadingIndicator.js.map +1 -1
- package/dist/src/ui/hooks/useLoadingIndicator.test.js +2 -0
- package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.js +2 -2
- package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
- package/dist/src/ui/hooks/useQuotaAndFallback.test.js +3 -3
- package/dist/src/ui/hooks/useSelectionList.js +24 -1
- package/dist/src/ui/hooks/useSelectionList.js.map +1 -1
- package/dist/src/ui/hooks/useSelectionList.test.js +63 -1
- package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
- package/dist/src/ui/types.d.ts +2 -1
- package/dist/src/ui/types.js.map +1 -1
- package/dist/src/ui/utils/CodeColorizer.d.ts +1 -0
- package/dist/src/ui/utils/CodeColorizer.js +13 -7
- package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
- package/dist/src/ui/utils/TableRenderer.js +3 -2
- package/dist/src/ui/utils/TableRenderer.js.map +1 -1
- package/dist/src/ui/utils/borderStyles.d.ts +1 -0
- package/dist/src/ui/utils/borderStyles.js +1 -0
- package/dist/src/ui/utils/borderStyles.js.map +1 -1
- package/dist/src/ui/utils/fileUtils.d.ts +4 -0
- package/dist/src/ui/utils/fileUtils.js +13 -0
- package/dist/src/ui/utils/fileUtils.js.map +1 -1
- package/dist/src/ui/utils/renderBorder.d.ts +27 -0
- package/dist/src/ui/utils/renderBorder.js +46 -0
- package/dist/src/ui/utils/renderBorder.js.map +1 -0
- package/dist/src/ui/utils/renderBorder.test.d.ts +6 -0
- package/dist/src/ui/utils/renderBorder.test.js +74 -0
- package/dist/src/ui/utils/renderBorder.test.js.map +1 -0
- package/dist/src/ui/utils/rewindFileOps.d.ts +9 -0
- package/dist/src/ui/utils/rewindFileOps.js +25 -1
- package/dist/src/ui/utils/rewindFileOps.js.map +1 -1
- package/dist/src/ui/utils/rewindFileOps.test.js +94 -1
- package/dist/src/ui/utils/rewindFileOps.test.js.map +1 -1
- package/dist/src/ui/utils/terminalCapabilityManager.js +7 -0
- package/dist/src/ui/utils/terminalCapabilityManager.js.map +1 -1
- package/dist/src/ui/utils/terminalCapabilityManager.stdin-flow.test.d.ts +6 -0
- package/dist/src/ui/utils/terminalCapabilityManager.stdin-flow.test.js +105 -0
- package/dist/src/ui/utils/terminalCapabilityManager.stdin-flow.test.js.map +1 -0
- package/dist/src/utils/commentJson.js +66 -5
- package/dist/src/utils/commentJson.js.map +1 -1
- package/dist/src/utils/commentJson.test.js +21 -0
- package/dist/src/utils/commentJson.test.js.map +1 -1
- package/dist/src/utils/handleAutoUpdate.test.js +2 -2
- package/dist/src/utils/persistentState.js +84 -15
- package/dist/src/utils/persistentState.js.map +1 -1
- package/dist/src/utils/persistentState.test.js +66 -9
- package/dist/src/utils/persistentState.test.js.map +1 -1
- package/dist/src/utils/persistentStateRecovery.test.d.ts +6 -0
- package/dist/src/utils/persistentStateRecovery.test.js +84 -0
- package/dist/src/utils/persistentStateRecovery.test.js.map +1 -0
- package/dist/src/utils/processUtils.d.ts +10 -1
- package/dist/src/utils/processUtils.js +55 -1
- package/dist/src/utils/processUtils.js.map +1 -1
- package/dist/src/utils/processUtils.test.js +90 -2
- package/dist/src/utils/processUtils.test.js.map +1 -1
- package/dist/src/utils/relaunch.js +7 -0
- package/dist/src/utils/relaunch.js.map +1 -1
- package/dist/src/utils/relaunch.test.js +16 -0
- package/dist/src/utils/relaunch.test.js.map +1 -1
- package/dist/src/utils/retry.d.ts +22 -0
- package/dist/src/utils/retry.js +54 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/utils/retry.test.d.ts +6 -0
- package/dist/src/utils/retry.test.js +141 -0
- package/dist/src/utils/retry.test.js.map +1 -0
- package/dist/src/utils/sandbox-macos-permissive-open.sb +36 -1
- package/dist/src/utils/sandbox-macos-permissive-proxied.sb +36 -1
- package/dist/src/utils/sandbox-macos-profiles.test.js +93 -0
- package/dist/src/utils/sandbox-macos-profiles.test.js.map +1 -1
- package/dist/src/utils/sandbox-macos-restrictive-open.sb +39 -1
- package/dist/src/utils/sandbox-macos-restrictive-proxied.sb +40 -1
- package/dist/src/utils/sandbox-macos-strict-open.sb +38 -2
- package/dist/src/utils/sandbox-macos-strict-proxied.sb +38 -2
- package/dist/src/utils/sandbox.js +149 -36
- package/dist/src/utils/sandbox.js.map +1 -1
- package/dist/src/utils/sandbox.test.js +410 -2
- package/dist/src/utils/sandbox.test.js.map +1 -1
- package/dist/src/utils/sandboxBuiltinProfiles.js +198 -8
- package/dist/src/utils/sandboxBuiltinProfiles.js.map +1 -1
- package/dist/src/utils/sandboxUtils.d.ts +25 -0
- package/dist/src/utils/sandboxUtils.js +225 -1
- package/dist/src/utils/sandboxUtils.js.map +1 -1
- package/dist/src/utils/sandboxUtils.test.js +341 -1
- package/dist/src/utils/sandboxUtils.test.js.map +1 -1
- package/dist/src/utils/sessionUtils.d.ts +21 -1
- package/dist/src/utils/sessionUtils.js +66 -2
- package/dist/src/utils/sessionUtils.js.map +1 -1
- package/dist/src/utils/sessionUtils.test.js +143 -0
- package/dist/src/utils/sessionUtils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -118,7 +118,11 @@ describe('sandbox', () => {
|
|
|
118
118
|
vi.mocked(os.tmpdir).mockReturnValue('/tmp');
|
|
119
119
|
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
120
120
|
vi.mocked(fs.realpathSync).mockImplementation((p) => p);
|
|
121
|
+
vi.mocked(fs.mkdtempSync).mockImplementation((prefix) => `${prefix}test-tmp`);
|
|
122
|
+
vi.mocked(fs.chmodSync).mockImplementation(() => { });
|
|
123
|
+
vi.mocked(fs.rmSync).mockImplementation(() => { });
|
|
121
124
|
vi.mocked(execSync).mockReturnValue(Buffer.from(''));
|
|
125
|
+
mockedHomedir.mockReturnValue('/home/user');
|
|
122
126
|
});
|
|
123
127
|
afterEach(() => {
|
|
124
128
|
process.env = originalEnv;
|
|
@@ -146,6 +150,73 @@ describe('sandbox', () => {
|
|
|
146
150
|
expect.stringContaining('sandbox-macos-permissive-open.sb'),
|
|
147
151
|
]), expect.objectContaining({ stdio: 'inherit' }));
|
|
148
152
|
});
|
|
153
|
+
it('should isolate temporary directory for macOS seatbelt (sandbox-exec)', async () => {
|
|
154
|
+
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
155
|
+
vi.mocked(os.tmpdir).mockReturnValue('/var/folders/test/T');
|
|
156
|
+
vi.mocked(fs.realpathSync).mockImplementation((p) => p);
|
|
157
|
+
vi.mocked(fs.mkdtempSync).mockReturnValue('/var/folders/test/T/cell-sandbox-tmp-123456');
|
|
158
|
+
const config = createMockSandboxConfig({
|
|
159
|
+
command: 'sandbox-exec',
|
|
160
|
+
image: 'some-image',
|
|
161
|
+
});
|
|
162
|
+
const mockSpawnProcess = new EventEmitter();
|
|
163
|
+
mockSpawnProcess.stdout = new EventEmitter();
|
|
164
|
+
mockSpawnProcess.stderr = new EventEmitter();
|
|
165
|
+
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
|
166
|
+
const onSpy = vi.spyOn(process, 'on');
|
|
167
|
+
const offSpy = vi.spyOn(process, 'off');
|
|
168
|
+
const promise = start_sandbox(config, [], undefined, ['arg1']);
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
mockSpawnProcess.emit('close', 0);
|
|
171
|
+
}, 10);
|
|
172
|
+
await expect(promise).resolves.toBe(0);
|
|
173
|
+
// Verify that an isolated temporary directory was created
|
|
174
|
+
expect(fs.mkdtempSync).toHaveBeenCalledWith(expect.stringContaining(path.join('/var/folders/test/T', 'cell-sandbox-')));
|
|
175
|
+
const spawnCalls = vi.mocked(spawn).mock.calls;
|
|
176
|
+
const spawnArgs = spawnCalls[0]?.[1];
|
|
177
|
+
// Verify that TMP_DIR argument passed to seatbelt is the isolated temp directory, NOT host tmpdir
|
|
178
|
+
expect(spawnArgs).toContain('TMP_DIR=/var/folders/test/T/cell-sandbox-tmp-123456');
|
|
179
|
+
expect(spawnArgs).not.toContain('TMP_DIR=/var/folders/test/T');
|
|
180
|
+
// Verify that TMPDIR environment variable is set in the sandboxed shell execution
|
|
181
|
+
const shCommand = spawnArgs[spawnArgs.indexOf('sh') + 2];
|
|
182
|
+
expect(shCommand).toContain('TMPDIR=');
|
|
183
|
+
expect(shCommand).toContain('/var/folders/test/T/cell-sandbox-tmp-123456');
|
|
184
|
+
// Verify cleanup of the isolated temporary directory
|
|
185
|
+
expect(fs.rmSync).toHaveBeenCalledWith('/var/folders/test/T/cell-sandbox-tmp-123456', expect.objectContaining({ recursive: true, force: true }));
|
|
186
|
+
// Verify that exit and signal cleanup hooks are registered and unregistered
|
|
187
|
+
expect(onSpy).toHaveBeenCalledWith('exit', expect.any(Function));
|
|
188
|
+
expect(onSpy).toHaveBeenCalledWith('SIGINT', expect.any(Function));
|
|
189
|
+
expect(onSpy).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
|
|
190
|
+
expect(offSpy).toHaveBeenCalledWith('exit', expect.any(Function));
|
|
191
|
+
expect(offSpy).toHaveBeenCalledWith('SIGINT', expect.any(Function));
|
|
192
|
+
expect(offSpy).toHaveBeenCalledWith('SIGTERM', expect.any(Function));
|
|
193
|
+
onSpy.mockRestore();
|
|
194
|
+
offSpy.mockRestore();
|
|
195
|
+
});
|
|
196
|
+
it('should safely swallow errors if fs.rmSync fails during cleanup', async () => {
|
|
197
|
+
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
198
|
+
vi.mocked(os.tmpdir).mockReturnValue('/var/folders/test/T');
|
|
199
|
+
vi.mocked(fs.realpathSync).mockImplementation((p) => p);
|
|
200
|
+
vi.mocked(fs.mkdtempSync).mockReturnValue('/var/folders/test/T/cell-sandbox-tmp-error');
|
|
201
|
+
vi.mocked(fs.rmSync).mockImplementation(() => {
|
|
202
|
+
throw new Error('EPERM: operation not permitted');
|
|
203
|
+
});
|
|
204
|
+
const config = createMockSandboxConfig({
|
|
205
|
+
command: 'sandbox-exec',
|
|
206
|
+
image: 'some-image',
|
|
207
|
+
});
|
|
208
|
+
const mockSpawnProcess = new EventEmitter();
|
|
209
|
+
mockSpawnProcess.stdout = new EventEmitter();
|
|
210
|
+
mockSpawnProcess.stderr = new EventEmitter();
|
|
211
|
+
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
|
212
|
+
const promise = start_sandbox(config, [], undefined, ['arg1']);
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
mockSpawnProcess.emit('close', 0);
|
|
215
|
+
}, 10);
|
|
216
|
+
// Even if fs.rmSync throws, start_sandbox should resolve successfully and not crash
|
|
217
|
+
await expect(promise).resolves.toBe(0);
|
|
218
|
+
expect(fs.rmSync).toHaveBeenCalled();
|
|
219
|
+
});
|
|
149
220
|
it('should resolve custom seatbelt profile from user home directory', async () => {
|
|
150
221
|
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
151
222
|
vi.stubEnv('SEATBELT_PROFILE', 'custom-test');
|
|
@@ -330,6 +401,122 @@ describe('sandbox', () => {
|
|
|
330
401
|
`SANDBOX=${containerName}`,
|
|
331
402
|
]), expect.objectContaining({ stdio: 'inherit' }));
|
|
332
403
|
});
|
|
404
|
+
it('should not attempt to create a temporary settings directory when homedir is empty', async () => {
|
|
405
|
+
mockedHomedir.mockReturnValue('');
|
|
406
|
+
vi.mocked(os.tmpdir).mockReturnValue('/mock/tmp');
|
|
407
|
+
const config = createMockSandboxConfig({
|
|
408
|
+
command: 'docker',
|
|
409
|
+
image: 'cell-cli-sandbox',
|
|
410
|
+
});
|
|
411
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
412
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
413
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
414
|
+
setTimeout(() => {
|
|
415
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
416
|
+
mockImageCheckProcess.emit('close', 0);
|
|
417
|
+
}, 1);
|
|
418
|
+
return mockImageCheckProcess;
|
|
419
|
+
});
|
|
420
|
+
const mockSpawnProcess = new EventEmitter();
|
|
421
|
+
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
|
422
|
+
if (event === 'close') {
|
|
423
|
+
setTimeout(() => cb(0), 10);
|
|
424
|
+
}
|
|
425
|
+
return mockSpawnProcess;
|
|
426
|
+
});
|
|
427
|
+
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
428
|
+
await expect(start_sandbox(config, [], undefined, ['arg1'])).resolves.toBe(0);
|
|
429
|
+
// Should only create the sandbox temp directory, not the settings fallback directory
|
|
430
|
+
expect(fs.mkdtempSync).toHaveBeenCalledTimes(1);
|
|
431
|
+
expect(fs.mkdtempSync).toHaveBeenCalledWith(path.join('/mock/tmp', 'cell-sandbox-'));
|
|
432
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(expect.stringMatching(/settings\.json$/), expect.any(String), { mode: 0o600 });
|
|
433
|
+
const dockerCall = vi.mocked(spawn).mock.calls.find((call) => {
|
|
434
|
+
const args = call[1];
|
|
435
|
+
return args && args.includes('run');
|
|
436
|
+
});
|
|
437
|
+
expect(dockerCall).toBeDefined();
|
|
438
|
+
const dockerArgs = dockerCall[1];
|
|
439
|
+
expect(dockerArgs.some((arg) => arg.includes('.config/gcloud'))).toBe(false);
|
|
440
|
+
});
|
|
441
|
+
it('should not mount gcloud config directory when homedir is empty even if relative path exists', async () => {
|
|
442
|
+
mockedHomedir.mockReturnValue('');
|
|
443
|
+
vi.mocked(os.tmpdir).mockReturnValue('/mock/tmp');
|
|
444
|
+
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
|
445
|
+
if (typeof p === 'string' && p.includes('.config')) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
});
|
|
450
|
+
const config = createMockSandboxConfig({
|
|
451
|
+
command: 'docker',
|
|
452
|
+
image: 'cell-cli-sandbox',
|
|
453
|
+
});
|
|
454
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
455
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
456
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
457
|
+
setTimeout(() => {
|
|
458
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
459
|
+
mockImageCheckProcess.emit('close', 0);
|
|
460
|
+
}, 1);
|
|
461
|
+
return mockImageCheckProcess;
|
|
462
|
+
});
|
|
463
|
+
const mockSpawnProcess = new EventEmitter();
|
|
464
|
+
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
|
465
|
+
if (event === 'close') {
|
|
466
|
+
setTimeout(() => cb(0), 10);
|
|
467
|
+
}
|
|
468
|
+
return mockSpawnProcess;
|
|
469
|
+
});
|
|
470
|
+
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
471
|
+
await expect(start_sandbox(config, [], undefined, ['arg1'])).resolves.toBe(0);
|
|
472
|
+
const dockerCall = vi.mocked(spawn).mock.calls.find((call) => {
|
|
473
|
+
const args = call[1];
|
|
474
|
+
return args && args.includes('run');
|
|
475
|
+
});
|
|
476
|
+
expect(dockerCall).toBeDefined();
|
|
477
|
+
const dockerArgs = dockerCall[1];
|
|
478
|
+
expect(dockerArgs.some((arg) => arg.includes('.config/gcloud'))).toBe(false);
|
|
479
|
+
});
|
|
480
|
+
it('should mount gcloud config directory when homedir is defined and config exists', async () => {
|
|
481
|
+
mockedHomedir.mockReturnValue('/home/user');
|
|
482
|
+
vi.mocked(os.tmpdir).mockReturnValue('/mock/tmp');
|
|
483
|
+
const expectedConfigDir = path.join('/home/user', '.config', 'gcloud');
|
|
484
|
+
vi.mocked(fs.existsSync).mockImplementation((p) => {
|
|
485
|
+
if (typeof p === 'string' && p === expectedConfigDir) {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
return false;
|
|
489
|
+
});
|
|
490
|
+
const config = createMockSandboxConfig({
|
|
491
|
+
command: 'docker',
|
|
492
|
+
image: 'cell-cli-sandbox',
|
|
493
|
+
});
|
|
494
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
495
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
496
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
497
|
+
setTimeout(() => {
|
|
498
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
499
|
+
mockImageCheckProcess.emit('close', 0);
|
|
500
|
+
}, 1);
|
|
501
|
+
return mockImageCheckProcess;
|
|
502
|
+
});
|
|
503
|
+
const mockSpawnProcess = new EventEmitter();
|
|
504
|
+
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
|
505
|
+
if (event === 'close') {
|
|
506
|
+
setTimeout(() => cb(0), 10);
|
|
507
|
+
}
|
|
508
|
+
return mockSpawnProcess;
|
|
509
|
+
});
|
|
510
|
+
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
511
|
+
await expect(start_sandbox(config, [], undefined, ['arg1'])).resolves.toBe(0);
|
|
512
|
+
const dockerCall = vi.mocked(spawn).mock.calls.find((call) => {
|
|
513
|
+
const args = call[1];
|
|
514
|
+
return args && args.includes('run');
|
|
515
|
+
});
|
|
516
|
+
expect(dockerCall).toBeDefined();
|
|
517
|
+
const dockerArgs = dockerCall[1];
|
|
518
|
+
expect(dockerArgs.some((arg) => arg.includes(expectedConfigDir) && arg.endsWith(':ro'))).toBe(true);
|
|
519
|
+
});
|
|
333
520
|
it('should preserve the integration-test prefix for random container names', async () => {
|
|
334
521
|
const config = createMockSandboxConfig({
|
|
335
522
|
command: 'docker',
|
|
@@ -464,12 +651,78 @@ describe('sandbox', () => {
|
|
|
464
651
|
// The second call is 'docker run ...'
|
|
465
652
|
expect(spawn).toHaveBeenNthCalledWith(2, 'docker', expect.arrayContaining([
|
|
466
653
|
'run',
|
|
654
|
+
'--env',
|
|
655
|
+
'HOME=/home/node',
|
|
467
656
|
'--volume',
|
|
468
657
|
'/host/path:/container/path:ro',
|
|
469
658
|
'--volume',
|
|
470
|
-
expect.stringMatching(/[\\/]home[\\/]
|
|
659
|
+
expect.stringMatching(/[\\/]home[\\/]node[\\/]\.cell-cli:rw$/),
|
|
471
660
|
]), expect.any(Object));
|
|
472
661
|
});
|
|
662
|
+
it('should mount an isolated settings directory rather than exposing host credentials', async () => {
|
|
663
|
+
const config = createMockSandboxConfig({
|
|
664
|
+
command: 'docker',
|
|
665
|
+
image: 'cell-cli-sandbox',
|
|
666
|
+
});
|
|
667
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
668
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
669
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
670
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
673
|
+
mockImageCheckProcess.emit('close', 0);
|
|
674
|
+
}, 1);
|
|
675
|
+
return mockImageCheckProcess;
|
|
676
|
+
});
|
|
677
|
+
const mockSpawnProcess = new EventEmitter();
|
|
678
|
+
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
|
679
|
+
if (event === 'close') {
|
|
680
|
+
setTimeout(() => cb(0), 10);
|
|
681
|
+
}
|
|
682
|
+
return mockSpawnProcess;
|
|
683
|
+
});
|
|
684
|
+
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
685
|
+
await start_sandbox(config);
|
|
686
|
+
// Verify that docker run does NOT mount the raw host settings directory directly
|
|
687
|
+
expect(spawn).toHaveBeenNthCalledWith(2, 'docker', expect.not.arrayContaining([
|
|
688
|
+
'/home/user/.cell-cli:/home/node/.cell-cli',
|
|
689
|
+
]), expect.any(Object));
|
|
690
|
+
// Verify that docker run mounts the isolated sanitized settings directory
|
|
691
|
+
expect(spawn).toHaveBeenNthCalledWith(2, 'docker', expect.arrayContaining([
|
|
692
|
+
'--volume',
|
|
693
|
+
expect.stringMatching(/cell-sandbox.*:[\\/]home[\\/]node[\\/]\.cell-cli:rw$/),
|
|
694
|
+
]), expect.any(Object));
|
|
695
|
+
// Verify that ephemeral sandbox directory permissions are restricted to owner (0o700)
|
|
696
|
+
expect(fs.chmodSync).toHaveBeenCalledWith(expect.stringContaining('cell-sandbox-'), 0o700);
|
|
697
|
+
});
|
|
698
|
+
it('should tolerate chmod errors on non-POSIX filesystems without crashing', async () => {
|
|
699
|
+
const config = createMockSandboxConfig({
|
|
700
|
+
command: 'docker',
|
|
701
|
+
image: 'cell-cli-sandbox',
|
|
702
|
+
});
|
|
703
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
704
|
+
vi.mocked(fs.chmodSync).mockImplementationOnce(() => {
|
|
705
|
+
throw new Error('EPERM: operation not permitted');
|
|
706
|
+
});
|
|
707
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
708
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
709
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
710
|
+
setTimeout(() => {
|
|
711
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
712
|
+
mockImageCheckProcess.emit('close', 0);
|
|
713
|
+
}, 1);
|
|
714
|
+
return mockImageCheckProcess;
|
|
715
|
+
});
|
|
716
|
+
const mockSpawnProcess = new EventEmitter();
|
|
717
|
+
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
|
718
|
+
if (event === 'close') {
|
|
719
|
+
setTimeout(() => cb(0), 10);
|
|
720
|
+
}
|
|
721
|
+
return mockSpawnProcess;
|
|
722
|
+
});
|
|
723
|
+
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
724
|
+
await expect(start_sandbox(config)).resolves.toBe(0);
|
|
725
|
+
});
|
|
473
726
|
it('should handle allowedPaths in Docker', async () => {
|
|
474
727
|
const config = createMockSandboxConfig({
|
|
475
728
|
command: 'docker',
|
|
@@ -497,6 +750,161 @@ describe('sandbox', () => {
|
|
|
497
750
|
await start_sandbox(config);
|
|
498
751
|
expect(spawn).toHaveBeenCalledWith('docker', expect.arrayContaining(['--volume', '/extra/path:/extra/path:ro']), expect.any(Object));
|
|
499
752
|
});
|
|
753
|
+
it('should reject sensitive host mounts in SANDBOX_MOUNTS with FatalSandboxError', async () => {
|
|
754
|
+
const config = createMockSandboxConfig({
|
|
755
|
+
command: 'docker',
|
|
756
|
+
image: 'cell-cli-sandbox',
|
|
757
|
+
});
|
|
758
|
+
vi.stubEnv('SANDBOX_MOUNTS', '/home/user/.cell-cli:/mnt:rw');
|
|
759
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
760
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
761
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
762
|
+
setTimeout(() => {
|
|
763
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
764
|
+
mockImageCheckProcess.emit('close', 0);
|
|
765
|
+
}, 1);
|
|
766
|
+
return mockImageCheckProcess;
|
|
767
|
+
});
|
|
768
|
+
await expect(start_sandbox(config)).rejects.toThrow(FatalSandboxError);
|
|
769
|
+
});
|
|
770
|
+
it('should reject running container sandbox from a sensitive working directory with FatalSandboxError', async () => {
|
|
771
|
+
const config = createMockSandboxConfig({
|
|
772
|
+
command: 'docker',
|
|
773
|
+
image: 'cell-cli-sandbox',
|
|
774
|
+
});
|
|
775
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue('/home/user');
|
|
776
|
+
try {
|
|
777
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
778
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
779
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
780
|
+
setTimeout(() => {
|
|
781
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
782
|
+
mockImageCheckProcess.emit('close', 0);
|
|
783
|
+
}, 1);
|
|
784
|
+
return mockImageCheckProcess;
|
|
785
|
+
});
|
|
786
|
+
const targetWorkdir = path.resolve('/home/user');
|
|
787
|
+
await expect(start_sandbox(config)).rejects.toThrow(new RegExp(`Running sandbox from a sensitive host directory '${targetWorkdir.replace(/\\/g, '\\\\')}' is strictly prohibited`));
|
|
788
|
+
}
|
|
789
|
+
finally {
|
|
790
|
+
cwdSpy.mockRestore();
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
it('should reject running container sandbox when cliConfig targetDir is sensitive with FatalSandboxError', async () => {
|
|
794
|
+
const config = createMockSandboxConfig({
|
|
795
|
+
command: 'docker',
|
|
796
|
+
image: 'cell-cli-sandbox',
|
|
797
|
+
});
|
|
798
|
+
const targetDir = path.resolve('/home/user/.cell-cli');
|
|
799
|
+
const mockCliConfig = {
|
|
800
|
+
getTargetDir: vi.fn().mockReturnValue(targetDir),
|
|
801
|
+
getDebugMode: vi.fn().mockReturnValue(false),
|
|
802
|
+
};
|
|
803
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
804
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
805
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
806
|
+
setTimeout(() => {
|
|
807
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
808
|
+
mockImageCheckProcess.emit('close', 0);
|
|
809
|
+
}, 1);
|
|
810
|
+
return mockImageCheckProcess;
|
|
811
|
+
});
|
|
812
|
+
await expect(start_sandbox(config, [], mockCliConfig)).rejects.toThrow(new RegExp(`Running sandbox from a sensitive host directory '${targetDir.replace(/\\/g, '\\\\')}' is strictly prohibited`));
|
|
813
|
+
});
|
|
814
|
+
it('should reject running macOS seatbelt sandbox from a sensitive working directory with FatalSandboxError', async () => {
|
|
815
|
+
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
816
|
+
const config = createMockSandboxConfig({
|
|
817
|
+
command: 'sandbox-exec',
|
|
818
|
+
image: 'some-image',
|
|
819
|
+
});
|
|
820
|
+
const targetDir = path.resolve('/home/user');
|
|
821
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(targetDir);
|
|
822
|
+
try {
|
|
823
|
+
await expect(start_sandbox(config)).rejects.toThrow(new RegExp(`Running sandbox from a sensitive host directory '${targetDir.replace(/\\/g, '\\\\')}' is strictly prohibited`));
|
|
824
|
+
}
|
|
825
|
+
finally {
|
|
826
|
+
cwdSpy.mockRestore();
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
it('should reject running LXC sandbox from a sensitive working directory with FatalSandboxError', async () => {
|
|
830
|
+
const config = createMockSandboxConfig({
|
|
831
|
+
command: 'lxc',
|
|
832
|
+
image: 'cell-sandbox',
|
|
833
|
+
});
|
|
834
|
+
const targetDir = path.resolve('/home/user');
|
|
835
|
+
const cwdSpy = vi.spyOn(process, 'cwd').mockReturnValue(targetDir);
|
|
836
|
+
try {
|
|
837
|
+
await expect(start_sandbox(config)).rejects.toThrow(new RegExp(`Running sandbox from a sensitive host directory '${targetDir.replace(/\\/g, '\\\\')}' is strictly prohibited`));
|
|
838
|
+
}
|
|
839
|
+
finally {
|
|
840
|
+
cwdSpy.mockRestore();
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
it('should skip sensitive workspace directory in macOS seatbelt includedDirs', async () => {
|
|
844
|
+
vi.mocked(os.platform).mockReturnValue('darwin');
|
|
845
|
+
const config = createMockSandboxConfig({
|
|
846
|
+
command: 'sandbox-exec',
|
|
847
|
+
image: 'some-image',
|
|
848
|
+
});
|
|
849
|
+
const safeWorkspace = path.resolve('/safe/workspace');
|
|
850
|
+
const safeOther = path.resolve('/safe/other');
|
|
851
|
+
const sensitiveUser = path.resolve('/home/user');
|
|
852
|
+
const mockCliConfig = {
|
|
853
|
+
getTargetDir: vi.fn().mockReturnValue(safeWorkspace),
|
|
854
|
+
getDebugMode: vi.fn().mockReturnValue(false),
|
|
855
|
+
getWorkspaceContext: vi.fn().mockReturnValue({
|
|
856
|
+
getDirectories: vi
|
|
857
|
+
.fn()
|
|
858
|
+
.mockReturnValue([safeWorkspace, sensitiveUser, safeOther]),
|
|
859
|
+
}),
|
|
860
|
+
};
|
|
861
|
+
const mockSpawnProcess = new EventEmitter();
|
|
862
|
+
mockSpawnProcess.stdout = new EventEmitter();
|
|
863
|
+
mockSpawnProcess.stderr = new EventEmitter();
|
|
864
|
+
vi.mocked(spawn).mockReturnValue(mockSpawnProcess);
|
|
865
|
+
const promise = start_sandbox(config, [], mockCliConfig);
|
|
866
|
+
setTimeout(() => mockSpawnProcess.emit('close', 0), 10);
|
|
867
|
+
await promise;
|
|
868
|
+
const spawnArgs = vi.mocked(spawn).mock.calls[0][1];
|
|
869
|
+
expect(spawnArgs).toContain(`INCLUDE_DIR_0=${safeOther}`);
|
|
870
|
+
const includeDirs = spawnArgs.filter((arg) => arg.startsWith('INCLUDE_DIR_'));
|
|
871
|
+
expect(includeDirs.some((arg) => arg.includes(sensitiveUser))).toBe(false);
|
|
872
|
+
});
|
|
873
|
+
it('should ignore sensitive paths in allowedPaths', async () => {
|
|
874
|
+
const config = createMockSandboxConfig({
|
|
875
|
+
command: 'docker',
|
|
876
|
+
image: 'cell-cli-sandbox',
|
|
877
|
+
allowedPaths: ['/home/user/.cell-cli', '/safe/extra/path'],
|
|
878
|
+
});
|
|
879
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
880
|
+
const mockImageCheckProcess = new EventEmitter();
|
|
881
|
+
mockImageCheckProcess.stdout = new EventEmitter();
|
|
882
|
+
vi.mocked(spawn).mockImplementationOnce(() => {
|
|
883
|
+
setTimeout(() => {
|
|
884
|
+
mockImageCheckProcess.stdout.emit('data', Buffer.from('image-id'));
|
|
885
|
+
mockImageCheckProcess.emit('close', 0);
|
|
886
|
+
}, 1);
|
|
887
|
+
return mockImageCheckProcess;
|
|
888
|
+
});
|
|
889
|
+
const mockSpawnProcess = new EventEmitter();
|
|
890
|
+
mockSpawnProcess.on = vi.fn().mockImplementation((event, cb) => {
|
|
891
|
+
if (event === 'close') {
|
|
892
|
+
setTimeout(() => cb(0), 10);
|
|
893
|
+
}
|
|
894
|
+
return mockSpawnProcess;
|
|
895
|
+
});
|
|
896
|
+
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
897
|
+
await start_sandbox(config);
|
|
898
|
+
const runCalls = vi
|
|
899
|
+
.mocked(spawn)
|
|
900
|
+
.mock.calls.filter((call) => call[0] === 'docker' &&
|
|
901
|
+
Array.isArray(call[1]) &&
|
|
902
|
+
call[1].includes('run'));
|
|
903
|
+
expect(runCalls.length).toBe(1);
|
|
904
|
+
const runArgs = runCalls[0][1];
|
|
905
|
+
expect(runArgs).toContain('/safe/extra/path:/safe/extra/path:ro');
|
|
906
|
+
expect(runArgs.some((arg) => typeof arg === 'string' && arg.includes('/home/user/.cell-cli:'))).toBe(false);
|
|
907
|
+
});
|
|
500
908
|
it('should handle networkAccess: false in Docker', async () => {
|
|
501
909
|
const config = createMockSandboxConfig({
|
|
502
910
|
command: 'docker',
|
|
@@ -606,7 +1014,7 @@ describe('sandbox', () => {
|
|
|
606
1014
|
});
|
|
607
1015
|
vi.mocked(spawn).mockImplementationOnce(() => mockSpawnProcess);
|
|
608
1016
|
await start_sandbox(config);
|
|
609
|
-
expect(spawn).toHaveBeenCalledWith('docker', expect.arrayContaining(['--user', 'root', '--env', 'HOME=/home/
|
|
1017
|
+
expect(spawn).toHaveBeenCalledWith('docker', expect.arrayContaining(['--user', 'root', '--env', 'HOME=/home/node']), expect.any(Object));
|
|
610
1018
|
// Check that the entrypoint command includes the defensive useradd check
|
|
611
1019
|
const args = vi.mocked(spawn).mock.calls[1][1];
|
|
612
1020
|
const entrypointCmd = args[args.length - 1];
|