fix(cli): validate selected local paths before resolution

Reuse the nonempty NUL-free local path contract before normalization in artifact commands, auxiliary inputs, and output destinations. Empty selections now fail at parsing instead of selecting the working directory; explicit relative paths preserve their target.

Public CLI checks cover primary and auxiliary empty paths, exact relative-file digest, explicit current-directory selection, symlink parent semantics, and environment export inputs. Correct the desktop fixture to assert the existing CLI validation envelope.
This commit is contained in:
morluto
2026-10-24 07:28:09 +08:00
parent 231e458246
commit 9d7779d365
20 changed files with 229 additions and 58 deletions
+5
View File
@@ -39,6 +39,11 @@ pass an absolute path. REA reads and authenticates the file, then projects it
like retained Evidence, so a large file is bounded by the file reader rather
than charged as inline input.
Local file inspection commands reject empty paths and NUL bytes before resolving
relative inputs, auxiliary files or output destinations. An empty string never
selects the working directory; use an explicit `.` when selecting that directory
is supported by the command.
For example, a 334 MB Obsidian application Evidence file yields a summary view
of about 10 KB. Replace the view with
`{"kind": "page", "collection": "modules", "offset": 0, "limit": 32}` for a
+2 -3
View File
@@ -10,6 +10,7 @@ import { AnalysisInputError } from "../domain/analysisErrorCore.js";
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
import { publishEvidenceDelivery } from "../application/EvidenceBundleCommands.js";
import { inspectAnalysisViewValidated } from "../application/analysisView/AnalysisViewService.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
@@ -18,9 +19,7 @@ export const analysisEvidenceDeliveryOptions = {
.enum(["complete", "summary"])
.default("complete")
.describe("Deliver complete Evidence, or a summary with --evidence_output"),
evidence_output: z
.string()
.min(1)
evidence_output: localPathStringSchema
.optional()
.describe(
"Save complete Evidence as a bundle; required for summary delivery. Never overwrites.",
+4 -1
View File
@@ -8,6 +8,7 @@ import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { safeParseJson } from "../domain/safeJson.js";
import { logCliCommand } from "../cliLogging.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
@@ -25,7 +26,9 @@ export const registerApkSignatureCommands = (
cli.command(apkSignatureModule.cliCommand, {
description:
"Inspect declared APK signing schemes, signers and content digests",
args: z.object({ path: z.string().describe("Selected local APK/ZIP/AAB") }),
args: z.object({
path: localPathStringSchema.describe("Selected local APK/ZIP/AAB"),
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
view: z
+20 -9
View File
@@ -22,6 +22,7 @@ import { inspectNativeExportsInputSchema } from "../domain/native/nativeExports.
import { inspectElfNotesInputSchema } from "../domain/native/elfNotes.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import {
analysisEvidenceDeliveryOptions,
@@ -37,7 +38,9 @@ export const registerBinaryDiagnosticsCommands = (
cli.command(CLI_COMMANDS.inspectElfNotes, {
description:
"Inspect ELF PT_NOTE and SHT_NOTE identities without executing the image",
args: z.object({ path: z.string().describe("Local ELF32/ELF64 image") }),
args: z.object({
path: localPathStringSchema.describe("Local ELF32/ELF64 image"),
}),
options: z.object({
maxFileBytes: inspectElfNotesInputSchema.shape.max_file_bytes,
maxEntries: inspectElfNotesInputSchema.shape.max_entries,
@@ -71,7 +74,7 @@ export const registerBinaryDiagnosticsCommands = (
cli.command(CLI_COMMANDS.inspectByteEntropy, {
description:
"Inspect file-wide and block byte entropy without executing the file",
args: z.object({ path: z.string().describe("Local file") }),
args: z.object({ path: localPathStringSchema.describe("Local file") }),
options: z.object({
blockSize: inspectByteEntropyInputSchema.shape.block_size.describe(
"Power-of-two block size from 256 to 65536 bytes (default 4096)",
@@ -108,7 +111,9 @@ export const registerBinaryDiagnosticsCommands = (
cli.command(CLI_COMMANDS.inspectPeCertificates, {
description:
"Inspect PE Authenticode certificate table identities without verifying signatures",
args: z.object({ path: z.string().describe("Local PE32/PE32+ image") }),
args: z.object({
path: localPathStringSchema.describe("Local PE32/PE32+ image"),
}),
options: z.object({
maxFileBytes: inspectPeCertificatesInputSchema.shape.max_file_bytes,
maxEntries: inspectPeCertificatesInputSchema.shape.max_entries,
@@ -141,7 +146,9 @@ export const registerBinaryDiagnosticsCommands = (
cli.command(CLI_COMMANDS.inspectPeResources, {
description:
"Inspect PE resource identities, payload ranges, hashes and icon-group relationships",
args: z.object({ path: z.string().describe("Local PE32/PE32+ image") }),
args: z.object({
path: localPathStringSchema.describe("Local PE32/PE32+ image"),
}),
options: z.object({
maxFileBytes: inspectPeResourcesInputSchema.shape.max_file_bytes,
maxEntries: inspectPeResourcesInputSchema.shape.max_entries,
@@ -174,7 +181,9 @@ export const registerBinaryDiagnosticsCommands = (
cli.command(CLI_COMMANDS.inspectNativeExports, {
description:
"Inspect a PE or ELF image's export table without executing it",
args: z.object({ path: z.string().describe("Local PE or ELF image") }),
args: z.object({
path: localPathStringSchema.describe("Local PE or ELF image"),
}),
options: z.object({
maxFileBytes: inspectNativeExportsInputSchema.shape.max_file_bytes,
...analysisEvidenceDeliveryOptions,
@@ -210,7 +219,9 @@ export const registerBinaryDiagnosticsCommands = (
description:
"Inspect binary file/linked layout without executing the selected object",
args: z.object({
path: z.string().describe("Local ELF binary or relocatable object"),
path: localPathStringSchema.describe(
"Local ELF binary or relocatable object",
),
}),
options: z.object({
pwntoolsTimeoutMs:
@@ -237,9 +248,9 @@ export const registerBinaryDiagnosticsCommands = (
description:
"Inspect supplied core registers/signals and optional debugger mapping evidence",
args: z.object({
path: z
.string()
.describe("Local Linux ELF64 amd64 or AArch64 little-endian core"),
path: localPathStringSchema.describe(
"Local Linux ELF64 amd64 or AArch64 little-endian core",
),
}),
options: z.object({
debuggerContext: z
+4 -1
View File
@@ -13,6 +13,7 @@ import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
export const registerCaptureCommands = (
cli: CliInstance,
@@ -68,7 +69,9 @@ export const registerCaptureCommands = (
cli.command(CLI_COMMANDS.inspectPacketCapture, {
description:
"Inspect an existing packet capture with caller-supplied TShark",
args: z.object({ path: z.string().describe("Local packet capture") }),
args: z.object({
path: localPathStringSchema.describe("Local packet capture"),
}),
options: z
.object({
...packetCaptureOptionsSchema.shape,
+4 -4
View File
@@ -22,6 +22,7 @@ import { runCliJavaScriptApplicationAnalysis } from "./javascriptApplicationAnal
import { withCommandCancellation } from "./commandCancellation.js";
import { javascriptApplicationOptions } from "../cliObservationOptions.js";
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { registerDecompileDirectoryCommand } from "./decompileDirectoryCommand.js";
@@ -84,10 +85,9 @@ const registerOverviewCommands = (
cli.command(CLI_COMMANDS.analyze, {
description: "Get an overview of an app",
args: z.object({
path: z
.string()
.min(1)
.describe("App, program, or analysis database path"),
path: localPathStringSchema.describe(
"App, program, or analysis database path",
),
}),
options: overviewOptions.extend({
integrityPolicy: laneIntegrityPolicy,
+4 -6
View File
@@ -9,6 +9,7 @@ import {
nativeOpenOptionFields,
} from "./options.js";
import type { CliInstance } from "./types.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import type { JsonValue } from "../domain/jsonValue.js";
@@ -25,12 +26,9 @@ export const registerDecompileDirectoryCommand = (
path: z.string().describe("App or program path"),
}),
options: z.object({
output: z
.string()
.min(1)
.describe(
"Output directory. A relative path is resolved from the working directory. The parent must exist.",
),
output: localPathStringSchema.describe(
"Output directory. A relative path is resolved from the working directory. The parent must exist.",
),
layout: z
.enum(["address", "dwarf"])
.default("address")
+5 -7
View File
@@ -1,4 +1,5 @@
import { basename } from "node:path";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import type { EnvironmentExportResult } from "../application/environment/environmentExport.js";
import type { EnvironmentSource } from "../application/environment/environmentSource.js";
@@ -393,16 +394,13 @@ const registerExportCommand = (cli: CliInstance, logger: Logger): void => {
"Export an environment manifest and its capture Evidence as an agent environment bundle folder the agentenv framework runs with agent-env run",
args: z.object({
manifest: manifestArgument,
directory: z
.string()
.describe(
"Bundle directory to create; must be absent or empty, nothing is overwritten",
),
directory: localPathStringSchema.describe(
"Bundle directory to create; must be absent or empty, nothing is overwritten",
),
}),
options: z.object({
evidence: evidenceOption,
packageTarball: z
.string()
packageTarball: localPathStringSchema
.optional()
.describe(
"Packed rea-agents tarball (npm pack) copied into the env folder and installed by the Dockerfile instead of the registry package pinned to this version",
+2 -1
View File
@@ -4,6 +4,7 @@ import { logCliCommand } from "../cliLogging.js";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { inspectEvmInterfaceInputSchema } from "../domain/evm/evmInterface.js";
@@ -18,7 +19,7 @@ export const registerEvmCommands = (
cli.command(evmInterfaceModule.cliCommand, {
description: "Infer interface candidates from explicit local EVM bytecode",
args: z.object({
path: z.string().describe("Local bytecode carrier"),
path: localPathStringSchema.describe("Local bytecode carrier"),
encoding: z
.enum(["raw", "hex"])
.describe("Selected carrier representation"),
+4 -3
View File
@@ -9,6 +9,7 @@ import {
analysisEvidenceDeliveryOptions,
deliverCliAnalysisEvidence,
} from "./analysisEvidenceDelivery.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { machoSliceArchitectureSchema } from "../domain/apple/machoTargetSelection.js";
@@ -18,9 +19,9 @@ export const registerGoCommands = (cli: CliInstance, logger: Logger): void => {
description:
"Inspect embedded Go compiler, modules and build settings without target execution",
args: z.object({
path: z
.string()
.describe("Local ELF, PE, thin Mach-O or universal Mach-O image"),
path: localPathStringSchema.describe(
"Local ELF, PE, thin Mach-O or universal Mach-O image",
),
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
+4 -3
View File
@@ -6,6 +6,7 @@ import { safeParseJson } from "../domain/safeJson.js";
import { z } from "incur";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { createGpuCodeObjectsService } from "../composition/gpu.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
@@ -22,9 +23,9 @@ export const registerGpuCommands = (cli: CliInstance, logger: Logger): void => {
description:
"Inspect embedded CUDA/HIP targets and selected static kernel metadata",
args: z.object({
path: z
.string()
.describe("Local ELF/PE, CUDA fatbin, CUBIN, PTX or HIP bundle"),
path: localPathStringSchema.describe(
"Local ELF/PE, CUDA fatbin, CUBIN, PTX or HIP bundle",
),
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
+6 -7
View File
@@ -1,3 +1,4 @@
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { z } from "incur";
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
@@ -22,14 +23,13 @@ export const registerIl2cppCommands = (
description:
"Inventory IL2CPP global-metadata.dat names, owners and string literals, optionally linking method addresses from the native binary",
args: z.object({
path: z
.string()
.describe("Local global-metadata.dat (version 24, 27, 29, 31 or 39)"),
path: localPathStringSchema.describe(
"Local global-metadata.dat (version 24, 27, 29, 31 or 39)",
),
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
binary: z
.string()
binary: localPathStringSchema
.optional()
.describe(
"Matching native binary (Mach-O GameAssembly.dylib, PE GameAssembly.dll or ELF libil2cpp.so) to link method addresses; read only",
@@ -38,8 +38,7 @@ export const registerIl2cppCommands = (
.enum(["arm64", "amd64"])
.optional()
.describe("Slice of a universal Mach-O binary"),
ghidraSeeds: z
.string()
ghidraSeeds: localPathStringSchema
.optional()
.describe(
"Also write REA_GHIDRA_SEED_FILE function seeds named Type$$Method for every linked method; needs --binary",
+4 -1
View File
@@ -6,6 +6,7 @@ import { safeParseJson } from "../domain/safeJson.js";
import { z } from "incur";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { createModelArtifactService } from "../composition/model.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
@@ -25,7 +26,9 @@ export const registerModelCommands = (
description:
"Inspect GGUF or safetensors headers without loading model weights",
args: z.object({
path: z.string().describe("Local model file, detected by content"),
path: localPathStringSchema.describe(
"Local model file, detected by content",
),
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
+2 -1
View File
@@ -1,4 +1,5 @@
import { z } from "incur";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { createPythonPycService } from "../composition/pythonPyc.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
@@ -18,7 +19,7 @@ export const registerPythonPycCommands = (
description:
"Inspect a supported CPython 3.7–3.13 pyc header and marshal code objects without executing bytecode",
args: z.object({
path: z.string().describe("Local CPython .pyc file"),
path: localPathStringSchema.describe("Local CPython .pyc file"),
}),
options: z.object({
maxFileBytes: inspectPythonPycInputSchema.shape.max_file_bytes,
+4 -3
View File
@@ -14,6 +14,7 @@ import { safeParseJson } from "../domain/safeJson.js";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { machoSliceArchitectureSchema } from "../domain/apple/machoTargetSelection.js";
@@ -26,9 +27,9 @@ export const registerRustCommands = (
description:
"Inspect an embedded cargo-auditable Rust dependency list without target execution",
args: z.object({
path: z
.string()
.describe("Local ELF, PE, thin Mach-O or universal Mach-O image"),
path: localPathStringSchema.describe(
"Local ELF, PE, thin Mach-O or universal Mach-O image",
),
}),
options: z.object({
...analysisEvidenceDeliveryOptions,
+4 -1
View File
@@ -13,6 +13,7 @@ import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
export const registerSignalCommands = (
cli: CliInstance,
@@ -70,7 +71,9 @@ export const registerSignalCommands = (
cli.command(CLI_COMMANDS.decodeSignalRecording, {
description:
"Decode an existing signal recording with caller-supplied sigrok",
args: z.object({ path: z.string().describe("Local signal recording") }),
args: z.object({
path: localPathStringSchema.describe("Local signal recording"),
}),
options: z
.object({
...signalRecordingOptionsSchema.shape,
+2 -1
View File
@@ -5,6 +5,7 @@ import { logCliCommand } from "../cliLogging.js";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
import { withCommandCancellation } from "./commandCancellation.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { inspectSqliteDatabaseInputSchema } from "../domain/sqlite/sqliteDatabase.js";
@@ -20,7 +21,7 @@ export const registerSqliteCommands = (
description:
"Inspect SQLite schema and optional ordinary-table rows from a local snapshot",
args: z.object({
path: z.string().describe("Local SQLite database snapshot"),
path: localPathStringSchema.describe("Local SQLite database snapshot"),
}),
options: z.object({
workerTimeoutMs: inspectSqliteDatabaseInputSchema.shape.worker_timeout_ms,
+4 -4
View File
@@ -14,6 +14,7 @@ import {
analysisEvidenceDeliveryOptions,
deliverCliAnalysisEvidence,
} from "./analysisEvidenceDelivery.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { explicitSensitiveValueOptions } from "./explicitSensitiveArguments.js";
@@ -27,7 +28,7 @@ export const registerWebNetworkCaptureCommands = (
description:
"Inspect retained HAR, native mitmproxy, Fiddler SAZ or aioquic qlog records without fetching recorded URLs",
args: z.object({
capturePath: z.string().describe("Absolute local capture path"),
capturePath: localPathStringSchema.describe("Local capture path"),
format: z
.enum(WEB_NETWORK_CAPTURE_FORMATS)
.describe("Producer capture format"),
@@ -42,10 +43,9 @@ export const registerWebNetworkCaptureCommands = (
.array(z.string())
.optional()
.describe("Exact body pointers; repeat to select several"),
protobufSchema: z
.string()
protobufSchema: localPathStringSchema
.optional()
.describe("Absolute local compiled FileDescriptorSet path"),
.describe("Local compiled FileDescriptorSet path"),
protobufMessage: z
.string()
.optional()
@@ -0,0 +1,141 @@
import { createHash } from "node:crypto";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { parseEvidence } from "../../src/domain/evidence.js";
import { pythonPycFixture } from "../../src/python/PythonPyc.fixture.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
const fileCommands = [
"inspect-sqlite-database",
"inspect-python-pyc",
"inspect-apk-signature",
"inspect-packet-capture",
"decode-signal-recording",
"inspect-model-artifact",
"inspect-elf-notes",
"inspect-byte-entropy",
"inspect-pe-resources",
"inspect-pe-certificates",
"inspect-native-exports",
"inspect-binary-layout",
"inspect-recorded-crash",
"inspect-go-binary",
"inspect-rust-binary",
"inspect-gpu-code-objects",
"inspect-il2cpp-metadata",
] as const;
cliTest(
"rejects empty file selections before they can become the working directory",
async ({ cli }) => {
const cwd = await createTestTempDirectory("rea-empty-local-path-");
const selections = [
...fileCommands.map((command) => ({
arguments: [command, ""],
field: "path",
})),
{ arguments: ["inspect-evm-interface", "", "hex"], field: "path" },
{
arguments: ["inspect-web-network-capture", "", "har"],
field: "capturePath",
},
];
for (const selection of selections) {
const result = await runFileCli(cli, {
arguments: [...selection.arguments, "--json"],
cwd,
});
expect(result.exitCode, selection.arguments[0]).toBe(1);
expect(result.json, selection.arguments[0]).toMatchObject({
code: "VALIDATION_ERROR",
fieldErrors: expect.arrayContaining([
expect.objectContaining({
path: selection.field,
code: "too_small",
missing: false,
}),
]),
});
}
},
);
cliTest(
"rejects empty auxiliary selections and output destinations before reading inputs",
async ({ cli }) => {
const cwd = await createTestTempDirectory("rea-empty-auxiliary-path-");
const selections = [
{
arguments: ["inspect-il2cpp-metadata", "unread", "--binary", ""],
field: "binary",
},
{
arguments: ["inspect-il2cpp-metadata", "unread", "--ghidra-seeds", ""],
field: "ghidraSeeds",
},
{
arguments: [
"inspect-web-network-capture",
"unread",
"har",
"--protobuf-schema",
"",
],
field: "protobufSchema",
},
{
arguments: ["decompile-all", "unread", "--output", ""],
field: "output",
},
{ arguments: ["environment-export", "unread", ""], field: "directory" },
{
arguments: [
"environment-export",
"unread",
"output",
"--package-tarball",
"",
],
field: "packageTarball",
},
];
for (const selection of selections) {
const result = await runFileCli(cli, {
arguments: [...selection.arguments, "--json"],
cwd,
});
expect(result.exitCode, selection.field).toBe(1);
expect(result.json, selection.field).toMatchObject({
code: "VALIDATION_ERROR",
fieldErrors: expect.arrayContaining([
expect.objectContaining({ path: selection.field, code: "too_small" }),
]),
});
}
},
);
cliTest(
"still inspects an explicit relative file with its exact digest",
async ({ cli }) => {
const cwd = await createTestTempDirectory("rea-relative-local-path-");
const bytes = pythonPycFixture();
const path = join(cwd, "selected.pyc");
await writeFile(path, bytes);
const result = await runFileCli(cli, {
arguments: ["inspect-python-pyc", "./selected.pyc", "--json"],
cwd,
});
expect(result.exitCode, result.stderr).toBe(0);
expect(parseEvidence(result.json)).toMatchObject({
operation: "inspect_python_pyc",
subject: {
local_path: path,
digest: { sha256: createHash("sha256").update(bytes).digest("hex") },
},
});
},
);
@@ -18,8 +18,10 @@ cliTest(
});
expect(empty.exitCode).not.toBe(0);
expect(empty.json).toMatchObject({
code: "invalid_request",
category: "invalid_input",
code: "VALIDATION_ERROR",
fieldErrors: expect.arrayContaining([
expect.objectContaining({ path: "path", code: "too_small" }),
]),
});
const explicit = await cli.run({
arguments: ["inspect-desktop-application", ".", "--json"],