diff --git a/src/utils/__tests__/simulator-steps-pid.test.ts b/src/utils/__tests__/simulator-steps-pid.test.ts index 8a240426..b501ef33 100644 --- a/src/utils/__tests__/simulator-steps-pid.test.ts +++ b/src/utils/__tests__/simulator-steps-pid.test.ts @@ -112,6 +112,24 @@ describe.sequential('launchSimulatorAppWithLogging PID resolution', () => { expect(result.processId).toBe(42567); }); + it('rejects bundle identifiers that would escape the OSLog predicate', async () => { + const spawner = vi.fn(createMockSpawner()); + const executor = vi.fn(createMockExecutor(42567)); + + const result = await launchSimulatorAppWithLogging( + 'test-sim-uuid', + 'com.evil" OR subsystem != "x', + executor, + undefined, + { spawner }, + ); + + expect(result.success).toBe(false); + expect(result.error).toMatch(/invalid.*bundle/i); + expect(spawner).not.toHaveBeenCalled(); + expect(executor).not.toHaveBeenCalled(); + }); + it('writes logs under the current workspace log directory when no test override is set', async () => { setSimulatorLogDirOverrideForTests(null); const spawner = createMockSpawner(); diff --git a/src/utils/simulator-steps.ts b/src/utils/simulator-steps.ts index 22196267..00a8fac2 100644 --- a/src/utils/simulator-steps.ts +++ b/src/utils/simulator-steps.ts @@ -14,6 +14,8 @@ import { stopSimulatorLaunchOsLogSessionsForApp, } from './log-capture/simulator-launch-oslog-sessions.ts'; +const VALID_LOG_SUBSYSTEM_PATTERN = /^[a-zA-Z0-9._-]+$/; + let logDirOverrideForTests: string | null = null; interface ResolvedSimulatorLogDir { @@ -152,6 +154,13 @@ export interface LaunchWithLoggingResult { error?: string; } +function validateLogSubsystem(value: string): string | undefined { + if (VALID_LOG_SUBSYSTEM_PATTERN.test(value)) { + return undefined; + } + return `Invalid bundle identifier: '${value}'. Bundle IDs must contain only alphanumeric characters, dots, hyphens, and underscores.`; +} + /** * Launch an app on a simulator with implicit runtime logging. * @@ -176,6 +185,11 @@ export async function launchSimulatorAppWithLogging( spawner?: ProcessSpawner; }, ): Promise { + const validationError = validateLogSubsystem(bundleId); + if (validationError) { + return { success: false, error: validationError }; + } + const spawner = deps?.spawner ?? spawn; const logsDir = resolveSimulatorLogDir();