Skip to content

Commit 383d45e

Browse files
sebastiondevcameroncooke
authored andcommitted
fix: validate bundleId and subsystem filters to prevent predicate injection in log capture
User-supplied bundleId and custom subsystem filter values are interpolated into NSPredicate strings passed to `log stream --predicate`. A bundleId containing double quotes or other special characters could inject arbitrary predicate syntax, altering log filtering behavior (information disclosure from other subsystems, or denial of service via malformed predicates). Add validation against a strict allowlist pattern (alphanumeric, dots, hyphens, underscores) before any string interpolation into predicates. Invalid values are rejected early with a descriptive error message. CWE-78 / predicate injection mitigation.
1 parent 1dd34db commit 383d45e

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

src/utils/__tests__/simulator-steps-pid.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,24 @@ describe.sequential('launchSimulatorAppWithLogging PID resolution', () => {
112112
expect(result.processId).toBe(42567);
113113
});
114114

115+
it('rejects bundle identifiers that would escape the OSLog predicate', async () => {
116+
const spawner = vi.fn(createMockSpawner());
117+
const executor = vi.fn(createMockExecutor(42567));
118+
119+
const result = await launchSimulatorAppWithLogging(
120+
'test-sim-uuid',
121+
'com.evil" OR subsystem != "x',
122+
executor,
123+
undefined,
124+
{ spawner },
125+
);
126+
127+
expect(result.success).toBe(false);
128+
expect(result.error).toMatch(/invalid.*bundle/i);
129+
expect(spawner).not.toHaveBeenCalled();
130+
expect(executor).not.toHaveBeenCalled();
131+
});
132+
115133
it('writes logs under the current workspace log directory when no test override is set', async () => {
116134
setSimulatorLogDirOverrideForTests(null);
117135
const spawner = createMockSpawner();

src/utils/simulator-steps.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
stopSimulatorLaunchOsLogSessionsForApp,
1515
} from './log-capture/simulator-launch-oslog-sessions.ts';
1616

17+
const VALID_LOG_SUBSYSTEM_PATTERN = /^[a-zA-Z0-9._-]+$/;
18+
1719
let logDirOverrideForTests: string | null = null;
1820

1921
interface ResolvedSimulatorLogDir {
@@ -152,6 +154,13 @@ export interface LaunchWithLoggingResult {
152154
error?: string;
153155
}
154156

157+
function validateLogSubsystem(value: string): string | undefined {
158+
if (VALID_LOG_SUBSYSTEM_PATTERN.test(value)) {
159+
return undefined;
160+
}
161+
return `Invalid bundle identifier: '${value}'. Bundle IDs must contain only alphanumeric characters, dots, hyphens, and underscores.`;
162+
}
163+
155164
/**
156165
* Launch an app on a simulator with implicit runtime logging.
157166
*
@@ -176,6 +185,11 @@ export async function launchSimulatorAppWithLogging(
176185
spawner?: ProcessSpawner;
177186
},
178187
): Promise<LaunchWithLoggingResult> {
188+
const validationError = validateLogSubsystem(bundleId);
189+
if (validationError) {
190+
return { success: false, error: validationError };
191+
}
192+
179193
const spawner = deps?.spawner ?? spawn;
180194

181195
const logsDir = resolveSimulatorLogDir();

0 commit comments

Comments
 (0)