fix(cli): reject non-decimal native UI and command deadline options (#8920)
This commit is contained in:
2 files changed
+140
-15
No files matched your search
+20
-15
@@ -43,13 +43,22 @@ export const registerUtilityCommands = (
|
||||
registerReferenceSourceCommand(cli, logger, environment);
|
||||
};
|
||||
|
||||
/**
|
||||
* A native deadline option keeps the shared field's bounds but admits only
|
||||
* decimal argv text; Incur's Number() coercion would accept "0x10" or "1e3".
|
||||
*/
|
||||
const cliNativeDeadline = (field: typeof nativeUiDeadlineField) =>
|
||||
cliIntegerArgument(1)
|
||||
.pipe(field.unwrap())
|
||||
.optional()
|
||||
.describe(field.description ?? "");
|
||||
|
||||
// Decimal tokens only, for the same reason as cliNativeDeadline.
|
||||
const nativeUiScopeOptions = {
|
||||
pid: z.number().int().positive().describe("Existing application PID"),
|
||||
windowId: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.describe("Exact selected application window ID"),
|
||||
pid: cliIntegerArgument(1).describe("Existing application PID"),
|
||||
windowId: cliIntegerArgument(1).describe(
|
||||
"Exact selected application window ID",
|
||||
),
|
||||
screenshot: z
|
||||
.boolean()
|
||||
.default(true)
|
||||
@@ -58,14 +67,10 @@ const nativeUiScopeOptions = {
|
||||
.boolean()
|
||||
.default(true)
|
||||
.describe("Read the selected window accessibility tree"),
|
||||
maxNodes: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.safe()
|
||||
maxNodes: cliIntegerArgument(1)
|
||||
.default(500)
|
||||
.describe("Maximum accessibility nodes per capture"),
|
||||
nativeUiDeadlineMs: nativeUiDeadlineField,
|
||||
nativeUiDeadlineMs: cliNativeDeadline(nativeUiDeadlineField),
|
||||
};
|
||||
|
||||
const nativeUiScope = (options: {
|
||||
@@ -203,7 +208,7 @@ const registerNativeCommands = (
|
||||
path: localPathStringSchema.describe("Mach-O or app path"),
|
||||
}),
|
||||
options: z.object({
|
||||
nativeCommandTimeoutMs: nativeCommandTimeoutField,
|
||||
nativeCommandTimeoutMs: cliNativeDeadline(nativeCommandTimeoutField),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
logCliCommand(logger, command, () =>
|
||||
@@ -230,7 +235,7 @@ const registerNativeCommands = (
|
||||
.describe(
|
||||
"Plist path relative to the app root, or an absolute path (default: the bundle's Info.plist)",
|
||||
),
|
||||
nativeCommandTimeoutMs: nativeCommandTimeoutField,
|
||||
nativeCommandTimeoutMs: cliNativeDeadline(nativeCommandTimeoutField),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
logCliCommand(logger, "inspect-plist", () =>
|
||||
@@ -258,7 +263,7 @@ const registerNativeCommands = (
|
||||
symbols: swiftSymbolsSchema.describe("Swift mangled symbols to demangle"),
|
||||
}),
|
||||
options: z.object({
|
||||
nativeCommandTimeoutMs: nativeCommandTimeoutField,
|
||||
nativeCommandTimeoutMs: cliNativeDeadline(nativeCommandTimeoutField),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
logCliCommand(logger, "demangle-swift", () =>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest, type CliResult } from "../../support/cli/cliFixture.js";
|
||||
|
||||
// Numeric coercion reads each of these as 16 (or 10 for "1e1").
|
||||
const NON_DECIMAL_TEXT = ["0x10", "0b10000", "1e1", "+16", "16.0", " 16"];
|
||||
|
||||
const nativeUi = (...flags: string[]): string[] => [
|
||||
"observe-native-ui",
|
||||
"Demo.app",
|
||||
"--pid=16",
|
||||
"--window-id=7",
|
||||
...flags,
|
||||
];
|
||||
|
||||
/** A present plist, so only option parsing can refuse at validation. */
|
||||
const plist = async (): Promise<string> => {
|
||||
const root = await createTestTempDirectory("rea-utility-options-");
|
||||
const path = join(root, "Info.plist");
|
||||
await writeFile(
|
||||
path,
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n<plist version="1.0"><dict/></plist>\n',
|
||||
);
|
||||
return path;
|
||||
};
|
||||
|
||||
const expectFieldRefusal = (result: CliResult, field: string): void => {
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(JSON.parse(result.stdout)).toMatchObject({
|
||||
code: "VALIDATION_ERROR",
|
||||
fieldErrors: expect.arrayContaining([
|
||||
expect.objectContaining({ path: field }),
|
||||
]),
|
||||
});
|
||||
};
|
||||
|
||||
const expectPastValidation = (result: CliResult): void => {
|
||||
expect(JSON.parse(result.stdout)).not.toMatchObject({
|
||||
code: "VALIDATION_ERROR",
|
||||
});
|
||||
};
|
||||
|
||||
describe("utility command integer options", () => {
|
||||
// The options share one parser, so one takes every malformed spelling.
|
||||
for (const [flag, field, values] of [
|
||||
["--pid", "pid", NON_DECIMAL_TEXT],
|
||||
["--window-id", "windowId", ["0x10"]],
|
||||
["--max-nodes", "maxNodes", ["0x10"]],
|
||||
["--native-ui-deadline-ms", "nativeUiDeadlineMs", ["0x10"]],
|
||||
] as const) {
|
||||
cliTest.for(values)(
|
||||
`rejects %j as a native UI ${flag} value`,
|
||||
async (value, { cli }) => {
|
||||
// A later flag replaces the decimal --pid/--window-id defaults.
|
||||
const result = await cli.run({
|
||||
arguments: [...nativeUi(`${flag}=${value}`), "--json"],
|
||||
});
|
||||
|
||||
expectFieldRefusal(result, field);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
cliTest('rejects "0x10" as a native command timeout', async ({ cli }) => {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
"inspect-plist",
|
||||
await plist(),
|
||||
"--native-command-timeout-ms=0x10",
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
|
||||
expectFieldRefusal(result, "nativeCommandTimeoutMs");
|
||||
});
|
||||
|
||||
cliTest(
|
||||
"keeps the native deadline bounds for decimal text",
|
||||
async ({ cli }) => {
|
||||
for (const value of ["0", "2147483648"]) {
|
||||
const result = await cli.run({
|
||||
arguments: [
|
||||
...nativeUi(`--native-ui-deadline-ms=${value}`),
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
expectFieldRefusal(result, "nativeUiDeadlineMs");
|
||||
}
|
||||
expectFieldRefusal(
|
||||
await cli.run({ arguments: [...nativeUi("--pid=0"), "--json"] }),
|
||||
"pid",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest("admits decimal utility integer options", async ({ cli }) => {
|
||||
expectPastValidation(
|
||||
await cli.run({
|
||||
arguments: [
|
||||
...nativeUi("--max-nodes=500", "--native-ui-deadline-ms=2147483647"),
|
||||
"--json",
|
||||
],
|
||||
}),
|
||||
);
|
||||
expectPastValidation(
|
||||
await cli.run({
|
||||
arguments: [
|
||||
"inspect-plist",
|
||||
await plist(),
|
||||
"--native-command-timeout-ms=1000",
|
||||
"--json",
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user