From b2ea9972aeb585aad7c847b05fe7b8c7eee4678e Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Wed, 25 Mar 2026 17:58:01 -0700 Subject: [PATCH 1/2] Add workspace config sanitization regression test --- Sources/TabManager.swift | 2 +- cmuxTests/WorkspaceUnitTests.swift | 86 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 418c07519ee7..5c78fede51be 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -2251,7 +2251,7 @@ class TabManager: ObservableObject { inheritedTerminalConfigForNewWorkspace(workspace: selectedWorkspace) } - private func inheritedTerminalConfigForNewWorkspace( + func inheritedTerminalConfigForNewWorkspace( workspace: Workspace? ) -> ghostty_surface_config_s? { if let panel = terminalPanelForWorkspaceConfigInheritanceSource(workspace: workspace), diff --git a/cmuxTests/WorkspaceUnitTests.swift b/cmuxTests/WorkspaceUnitTests.swift index ac727307fb82..c0090586fad1 100644 --- a/cmuxTests/WorkspaceUnitTests.swift +++ b/cmuxTests/WorkspaceUnitTests.swift @@ -511,6 +511,92 @@ final class WorkspaceCreationPlacementTests: XCTestCase { } } +@MainActor +final class WorkspaceCreationConfigSanitizationTests: XCTestCase { + private final class UnsafeConfigSnapshotTabManager: TabManager { + private var retainedCStringPointers: [UnsafeMutablePointer] = [] + private var retainedEnvVars: UnsafeMutablePointer? + private var injectedConfig: ghostty_surface_config_s? + var capturedConfigTemplate: ghostty_surface_config_s? + + deinit { + retainedEnvVars?.deinitialize(count: 1) + retainedEnvVars?.deallocate() + for pointer in retainedCStringPointers { + free(pointer) + } + } + + func installInjectedConfig(fontSize: Float) { + let workingDirectory = strdup("/tmp/cmux-workspace-snapshot") + let command = strdup("echo snapshot") + let envKey = strdup("CMUX_INHERITED_ENV") + let envValue = strdup("1") + let envVars = UnsafeMutablePointer.allocate(capacity: 1) + envVars.initialize( + to: ghostty_env_var_s( + key: UnsafePointer(envKey), + value: UnsafePointer(envValue) + ) + ) + + retainedCStringPointers = [workingDirectory, command, envKey, envValue].compactMap { $0 } + retainedEnvVars = envVars + + var config = ghostty_surface_config_new() + config.font_size = fontSize + config.working_directory = UnsafePointer(workingDirectory) + config.command = UnsafePointer(command) + config.env_vars = envVars + config.env_var_count = 1 + injectedConfig = config + } + + override func inheritedTerminalConfigForNewWorkspace( + workspace: Workspace? + ) -> ghostty_surface_config_s? { + injectedConfig ?? super.inheritedTerminalConfigForNewWorkspace(workspace: workspace) + } + + override func makeWorkspaceForCreation( + title: String, + workingDirectory: String?, + portOrdinal: Int, + configTemplate: ghostty_surface_config_s?, + initialTerminalCommand: String?, + initialTerminalEnvironment: [String: String] + ) -> Workspace { + capturedConfigTemplate = configTemplate + return super.makeWorkspaceForCreation( + title: title, + workingDirectory: workingDirectory, + portOrdinal: portOrdinal, + configTemplate: configTemplate, + initialTerminalCommand: initialTerminalCommand, + initialTerminalEnvironment: initialTerminalEnvironment + ) + } + } + + func testAddWorkspacePassesSanitizedInheritedConfigTemplate() { + let manager = UnsafeConfigSnapshotTabManager() + manager.installInjectedConfig(fontSize: 19) + + _ = manager.addWorkspace() + + guard let capturedConfig = manager.capturedConfigTemplate else { + XCTFail("Expected captured config template for new workspace") + return + } + + XCTAssertEqual(capturedConfig.font_size, 19, accuracy: 0.001) + XCTAssertNil(capturedConfig.working_directory) + XCTAssertNil(capturedConfig.command) + XCTAssertNil(capturedConfig.env_vars) + XCTAssertEqual(capturedConfig.env_var_count, 0) + } +} + final class WorkspaceTabColorSettingsTests: XCTestCase { func testNormalizedHexAcceptsAndNormalizesValidInput() { From a00e61752f74ae672491a86a881c7942956ad721 Mon Sep 17 00:00:00 2001 From: Lawrence Chen Date: Wed, 25 Mar 2026 15:25:47 -0700 Subject: [PATCH 2/2] Sanitize workspace creation config snapshots --- Sources/TabManager.swift | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 5c78fede51be..7d59d43d8c2c 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -816,7 +816,7 @@ class TabManager: ObservableObject { let selectedTabId: UUID? let selectedTabWasPinned: Bool let preferredWorkingDirectory: String? - let inheritedTerminalConfig: ghostty_surface_config_s? + let inheritedTerminalFontPoints: Float? } private var agentPIDSweepTimer: DispatchSourceTimer? private var workspaceGitMetadataPollTimer: DispatchSourceTimer? @@ -1218,7 +1218,9 @@ class TabManager: ObservableObject { sentryBreadcrumb("workspace.create", data: ["tabCount": nextTabCount]) let explicitWorkingDirectory = normalizedWorkingDirectory(overrideWorkingDirectory) let workingDirectory = explicitWorkingDirectory ?? snapshot.preferredWorkingDirectory - let inheritedConfig = snapshot.inheritedTerminalConfig + let inheritedConfig = workspaceCreationConfigTemplate( + inheritedTerminalFontPoints: snapshot.inheritedTerminalFontPoints + ) // Resolve placement against the pre-creation snapshot before Workspace init // boots terminal state. The ssh/new-workspace path can otherwise crash while // reading @Published placement state from existing workspaces mid-creation. @@ -2191,7 +2193,7 @@ class TabManager: ObservableObject { selectedTabId: currentSelectedTabId, selectedTabWasPinned: selectedTabSnapshot?.isPinned ?? false, preferredWorkingDirectory: preferredWorkingDirectoryForNewTab(workspace: selectedWorkspace), - inheritedTerminalConfig: inheritedTerminalConfigForNewWorkspace(workspace: selectedWorkspace) + inheritedTerminalFontPoints: inheritedTerminalFontPointsForNewWorkspace(workspace: selectedWorkspace) ) } @@ -2271,6 +2273,30 @@ class TabManager: ObservableObject { return nil } + private func inheritedTerminalFontPointsForNewWorkspace( + workspace: Workspace? + ) -> Float? { + guard let inheritedConfig = inheritedTerminalConfigForNewWorkspace(workspace: workspace), + inheritedConfig.font_size > 0 else { + return nil + } + return inheritedConfig.font_size + } + + private func workspaceCreationConfigTemplate( + inheritedTerminalFontPoints: Float? + ) -> ghostty_surface_config_s? { + guard let inheritedTerminalFontPoints, inheritedTerminalFontPoints > 0 else { + return nil + } + // ghostty_surface_config_s can carry raw C pointers owned by the source surface. + // New workspace creation only needs the inherited zoom level, so rebuild a clean + // config instead of snapshotting pointer-backed fields across workspace creation. + var config = ghostty_surface_config_new() + config.font_size = inheritedTerminalFontPoints + return config + } + private func normalizedWorkingDirectory(_ directory: String?) -> String? { guard let directory else { return nil } let normalized = normalizeDirectory(directory)