fix: validate browser geolocation numbers

This commit is contained in:
Peter Steinberger
2026-05-28 15:35:14 -04:00
parent 503d8d5542
commit 8fbdfc0a76
2 changed files with 72 additions and 19 deletions

View File

@@ -172,6 +172,31 @@ describe("browser state option collisions", () => {
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
});
it("rejects invalid geolocation numbers before dispatch", async () => {
await runBrowserCommand(["set", "geo", "48.208", "16.373", "--accuracy", "fast"]);
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
expectErrorMessage("Invalid --accuracy: must be a finite number");
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
});
it("passes valid decimal geolocation numbers", async () => {
const request = await runBrowserCommandAndGetRequest([
"set",
"geo",
"48.2082",
"16.3738",
"--accuracy",
"12.5",
]);
expect(request.body).toMatchObject({
latitude: 48.2082,
longitude: 16.3738,
accuracy: 12.5,
});
});
it("errors when headers JSON is missing", async () => {
await runBrowserCommand(["set", "headers"]);

View File

@@ -25,6 +25,22 @@ function parsePositiveInteger(value: unknown, label: string): number | undefined
return parsed;
}
function parseFiniteNumberOption(value: string | undefined, label: string): number | undefined {
if (value === undefined) {
return undefined;
}
const raw = value.trim();
const parsed = /^[+-]?(?:(?:\d+\.?\d*)|(?:\.\d+))(?:e[+-]?\d+)?$/i.test(raw)
? Number(raw)
: Number.NaN;
if (!Number.isFinite(parsed)) {
defaultRuntime.error(danger(`Invalid ${label}: must be a finite number`));
defaultRuntime.exit(1);
return undefined;
}
return parsed;
}
function runBrowserCommand(action: () => Promise<void>) {
return runCommandWithRuntime(defaultRuntime, action, (err) => {
defaultRuntime.error(danger(String(err)));
@@ -189,27 +205,39 @@ export function registerBrowserStateCommands(
.command("geo")
.description("Set geolocation (and grant permission)")
.option("--clear", "Clear geolocation + permissions", false)
.argument("[latitude]", "Latitude", (v: string) => Number(v))
.argument("[longitude]", "Longitude", (v: string) => Number(v))
.option("--accuracy <m>", "Accuracy in meters", (v: string) => Number(v))
.argument("[latitude]", "Latitude")
.argument("[longitude]", "Longitude")
.option("--accuracy <m>", "Accuracy in meters")
.option("--origin <origin>", "Origin to grant permissions for")
.option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (latitude: number | undefined, longitude: number | undefined, opts, cmd) => {
const parent = parentOpts(cmd);
await runBrowserSetRequest({
parent,
path: "/set/geolocation",
body: {
latitude: Number.isFinite(latitude) ? latitude : undefined,
longitude: Number.isFinite(longitude) ? longitude : undefined,
accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined,
origin: normalizeOptionalString(opts.origin),
clear: Boolean(opts.clear),
targetId: normalizeOptionalString(opts.targetId),
},
successMessage: opts.clear ? "geolocation cleared" : "geolocation set",
});
});
.action(
async (latitudeRaw: string | undefined, longitudeRaw: string | undefined, opts, cmd) => {
const parent = parentOpts(cmd);
const latitude = parseFiniteNumberOption(latitudeRaw, "latitude");
const longitude = parseFiniteNumberOption(longitudeRaw, "longitude");
const accuracy = parseFiniteNumberOption(opts.accuracy, "--accuracy");
if (
(latitudeRaw !== undefined && latitude === undefined) ||
(longitudeRaw !== undefined && longitude === undefined) ||
(opts.accuracy !== undefined && accuracy === undefined)
) {
return;
}
await runBrowserSetRequest({
parent,
path: "/set/geolocation",
body: {
latitude,
longitude,
accuracy,
origin: normalizeOptionalString(opts.origin),
clear: Boolean(opts.clear),
targetId: normalizeOptionalString(opts.targetId),
},
successMessage: opts.clear ? "geolocation cleared" : "geolocation set",
});
},
);
set
.command("media")