mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(cli): reject non-decimal pyc and BEAM limit options (#9060)
inspect-python-pyc and inspect-erlang-beam took their max-* limits through Number(), so 0x10 meant 16 and 1e2 meant 100. Admit decimal text only through cliDecimalField, which now keeps the shared field's description for help.
This commit is contained in:
4 files changed
+72
-9
No files matched your search
@@ -15,6 +15,7 @@ import {
|
||||
} from "./analysisEvidenceDelivery.js";
|
||||
import { erlangBeamDeliverySchema } from "../domain/analysisView/erlangBeamView.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
|
||||
/** Inspect operator-selected BEAM files through the same shared workflow as MCP. */
|
||||
export const registerErlangCommands = (
|
||||
@@ -28,9 +29,12 @@ export const registerErlangCommands = (
|
||||
path: localPathStringSchema.describe("Local BEAM file"),
|
||||
}),
|
||||
options: z.object({
|
||||
maxFileBytes: inspectErlangBeamInputSchema.shape.max_file_bytes,
|
||||
maxChunks: inspectErlangBeamInputSchema.shape.max_chunks,
|
||||
maxAtoms: inspectErlangBeamInputSchema.shape.max_atoms,
|
||||
// Decimal tokens only, as for inspect-python-pyc.
|
||||
maxFileBytes: cliDecimalField(
|
||||
inspectErlangBeamInputSchema.shape.max_file_bytes,
|
||||
),
|
||||
maxChunks: cliDecimalField(inspectErlangBeamInputSchema.shape.max_chunks),
|
||||
maxAtoms: cliDecimalField(inspectErlangBeamInputSchema.shape.max_atoms),
|
||||
...analysisEvidenceDeliveryOptions,
|
||||
view: z
|
||||
.string()
|
||||
|
||||
+6
-2
@@ -20,7 +20,7 @@ export const cliIntegerArgument = (min: number) =>
|
||||
.regex(/^[0-9]+$/u, "Expected a decimal integer")
|
||||
.pipe(z.coerce.number<string>().safe().min(min));
|
||||
|
||||
/** Parse a decimal token, then let the canonical numeric field own bounds and defaults. */
|
||||
/** Parse a decimal token, then let the canonical numeric field own bounds, defaults and help. */
|
||||
export function cliDecimalField(
|
||||
field: z.ZodDefault<z.ZodNumber>,
|
||||
): z.ZodType<number>;
|
||||
@@ -36,7 +36,11 @@ export function cliDecimalField(
|
||||
.pipe(z.coerce.number<string>())
|
||||
.pipe(field.unwrap());
|
||||
const omitted = field.parse(undefined);
|
||||
return omitted === undefined ? parsed.optional() : parsed.default(omitted);
|
||||
const admitted =
|
||||
omitted === undefined ? parsed.optional() : parsed.default(omitted);
|
||||
return field.description === undefined
|
||||
? admitted
|
||||
: admitted.describe(field.description);
|
||||
}
|
||||
|
||||
export const providerSelectionOption = analysisProviderSelectorSchema
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "./analysisEvidenceDelivery.js";
|
||||
import { pythonPycDeliverySchema } from "../domain/analysisView/pythonPycView.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { cliDecimalField } from "./options.js";
|
||||
|
||||
/** Inspect operator-selected pyc files through the same shared workflow as MCP. */
|
||||
export const registerPythonPycCommands = (
|
||||
@@ -28,10 +29,18 @@ export const registerPythonPycCommands = (
|
||||
path: localPathStringSchema.describe("Local CPython .pyc file"),
|
||||
}),
|
||||
options: z.object({
|
||||
maxFileBytes: inspectPythonPycInputSchema.shape.max_file_bytes,
|
||||
maxObjects: inspectPythonPycInputSchema.shape.max_objects,
|
||||
maxDepth: inspectPythonPycInputSchema.shape.max_depth,
|
||||
maxStringBytes: inspectPythonPycInputSchema.shape.max_string_bytes,
|
||||
// Decimal tokens only: numeric coercion would read "0x10" as 16 and
|
||||
// "1e2" as 100. The shared fields keep their bounds and defaults.
|
||||
maxFileBytes: cliDecimalField(
|
||||
inspectPythonPycInputSchema.shape.max_file_bytes,
|
||||
),
|
||||
maxObjects: cliDecimalField(
|
||||
inspectPythonPycInputSchema.shape.max_objects,
|
||||
),
|
||||
maxDepth: cliDecimalField(inspectPythonPycInputSchema.shape.max_depth),
|
||||
maxStringBytes: cliDecimalField(
|
||||
inspectPythonPycInputSchema.shape.max_string_bytes,
|
||||
),
|
||||
...analysisEvidenceDeliveryOptions,
|
||||
view: z
|
||||
.string()
|
||||
|
||||
@@ -250,6 +250,52 @@ describe("byte-entropy window options", () => {
|
||||
);
|
||||
});
|
||||
|
||||
describe("pyc and BEAM limit options", () => {
|
||||
// One option takes every malformed spelling. Each sibling takes one
|
||||
// spelling that numeric coercion admits within the field's bounds.
|
||||
for (const [command, flag, field, values] of [
|
||||
["inspect-python-pyc", "--max-depth", "maxDepth", NON_INTEGER_OPTION_TEXT],
|
||||
["inspect-python-pyc", "--max-file-bytes", "maxFileBytes", ["0x10"]],
|
||||
["inspect-python-pyc", "--max-objects", "maxObjects", ["1e2"]],
|
||||
["inspect-python-pyc", "--max-string-bytes", "maxStringBytes", ["0x1"]],
|
||||
["inspect-erlang-beam", "--max-file-bytes", "maxFileBytes", ["0x10"]],
|
||||
["inspect-erlang-beam", "--max-chunks", "maxChunks", ["1e1"]],
|
||||
["inspect-erlang-beam", "--max-atoms", "maxAtoms", ["0b1"]],
|
||||
] as const) {
|
||||
cliTest.for(values)(
|
||||
`rejects %j as ${command} ${flag}`,
|
||||
async (value, { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
arguments: [
|
||||
command,
|
||||
"/nonexistent/missing-artifact",
|
||||
`${flag}=${value}`,
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
|
||||
expectCliValidationError(result, field);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
cliTest.for([
|
||||
["inspect-python-pyc", "--max-depth=016"],
|
||||
["inspect-erlang-beam", "--max-chunks=016"],
|
||||
] as const)("keeps a decimal %s limit", async ([command, flag], { cli }) => {
|
||||
const result = await runFileCli(cli, {
|
||||
arguments: [command, "/nonexistent/missing-artifact", flag, "--json"],
|
||||
});
|
||||
|
||||
// The limit is admitted; the missing file is refused afterwards.
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(JSON.parse(result.stdout)).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: { issues: [expect.objectContaining({ path: ["path"] })] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("artifact decimal options", () => {
|
||||
// Same admission as byte-entropy windows. One option takes every malformed
|
||||
// spelling; siblings in artifactCommands.ts take one coercion each.
|
||||
|
||||
Reference in new issue
Block a user