mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 00:07:18 +00:00
fix(paths): enforce selected path contracts at admission (#9465)
This commit is contained in:
1 parent
b4b881116f
commit
25d3c7237f
18 files changed
+57
-93
No files matched your search
@@ -1,4 +1,3 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import { getHeapStatistics } from "node:v8";
|
||||
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
@@ -125,10 +124,6 @@ export const inspectApiContractEvidence = async (
|
||||
);
|
||||
if (!admitted.ok) return admitted;
|
||||
const input = admitted.value;
|
||||
if (!isAbsolute(input.path))
|
||||
return err(
|
||||
inputFailure("Expected an absolute filesystem path on this host."),
|
||||
);
|
||||
if (input.format === "graphql-sdl") {
|
||||
const { importGraphqlContract } =
|
||||
await import("./GraphqlContractImport.js");
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import { getHeapStatistics } from "node:v8";
|
||||
import { version } from "graphql";
|
||||
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
@@ -153,13 +152,6 @@ export const importGraphqlContract = async (
|
||||
resources: ArtifactResourceScope,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
if (input.operations_path !== undefined && !isAbsolute(input.operations_path))
|
||||
return err(
|
||||
inputFailure(
|
||||
"operations_path",
|
||||
"Expected an absolute filesystem path on this host.",
|
||||
),
|
||||
);
|
||||
const loaded = await readApiContract(input, resources, signal);
|
||||
if (!loaded.ok) return loaded;
|
||||
const artifact = {
|
||||
|
||||
@@ -4,15 +4,11 @@ import {
|
||||
projectRetainedAnalysis,
|
||||
type AnalysisViewRetention,
|
||||
} from "../analysisView/AnalysisViewService.js";
|
||||
import { isAbsolute } from "node:path";
|
||||
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { ApkSignaturePort } from "./ApkSignaturePort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||
import {
|
||||
apkSignatureMatchesSubject,
|
||||
apkSignatureResultSchema,
|
||||
@@ -48,16 +44,6 @@ export class ApkSignatureService {
|
||||
options?.signal,
|
||||
);
|
||||
if (!input.ok) return input;
|
||||
if (!isAbsolute(input.value.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const { detail, view, ...parameters } = input.value;
|
||||
if (detail === "summary") {
|
||||
const ready = requireAnalysisViewRetention(OPERATION, retention);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { DevicetreePort } from "./DevicetreePort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import {
|
||||
inspectDevicetreeInputSchema,
|
||||
@@ -37,16 +33,6 @@ export class DevicetreeService {
|
||||
options?.signal,
|
||||
);
|
||||
if (!input.ok) return input;
|
||||
if (!isAbsolute(input.value.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const result = await this.provider.inspect(input.value, options);
|
||||
if (!result.ok) return result;
|
||||
const report = devicetreeSchema.safeParse(result.value);
|
||||
|
||||
@@ -2,10 +2,7 @@ import {
|
||||
primaryAnalysisError,
|
||||
type AnalysisError,
|
||||
} from "../../domain/analysisErrorBase.js";
|
||||
import { isAbsolute } from "node:path";
|
||||
import { AnalysisInputError } from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence } from "../../domain/evidence.js";
|
||||
import { err } from "../../domain/result.js";
|
||||
import { inspectPlaywrightTraceInputSchema } from "../../domain/playwrightTrace/playwrightTraceQuery.js";
|
||||
import {
|
||||
PLAYWRIGHT_TRACE_OPERATION as operation,
|
||||
@@ -49,16 +46,6 @@ export class PlaywrightTraceService {
|
||||
signal,
|
||||
);
|
||||
if (!parsed.ok) return parsed;
|
||||
if (!isAbsolute(parsed.value.path))
|
||||
return err(
|
||||
new AnalysisInputError(operation, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem trace archive path.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const loaded = await this.port.inspect(parsed.value.path, signal);
|
||||
if (!loaded.ok) {
|
||||
const source =
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { PythonPycPort } from "./PythonPycPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import {
|
||||
inspectPythonPycInputSchema,
|
||||
@@ -37,16 +33,6 @@ export class PythonPycService {
|
||||
options?.signal,
|
||||
);
|
||||
if (!input.ok) return input;
|
||||
if (!isAbsolute(input.value.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const result = await this.provider.inspect(input.value, options);
|
||||
if (!result.ok) return result;
|
||||
const report = pythonPycSchema.safeParse(result.value);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirect
|
||||
import { PyInstallerArtifactReader } from "../PyInstallerArtifactReader.js";
|
||||
import { reconstructedPycHeader } from "./PythonBytecode.js";
|
||||
import { parsePythonPyc } from "../../python/PythonPycParser.js";
|
||||
import { inspectPythonPycInputSchema } from "../../domain/python/pythonPyc.js";
|
||||
import { pythonPycLimitsSchema } from "../../domain/python/pythonPyc.js";
|
||||
import {
|
||||
PYTHON_39_MAGIC,
|
||||
cArchiveExecutable,
|
||||
@@ -117,7 +117,7 @@ it("preserves legacy pyc bytes and derives only modern headerless CArchive bytec
|
||||
parsePythonPyc(
|
||||
bytes,
|
||||
{ path: module.name, bytes: bytes.length, sha256: sha256(bytes) },
|
||||
inspectPythonPycInputSchema.parse({ path: module.name }),
|
||||
pythonPycLimitsSchema.parse({}),
|
||||
).coverage.status,
|
||||
).toBe("complete");
|
||||
expect(await buffer(await reader.open(entry))).toEqual(module.bytes);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { pythonPycFixture } from "../../python/PythonPyc.fixture.js";
|
||||
import { parsePythonPyc } from "../../python/PythonPycParser.js";
|
||||
import { inspectPythonPycInputSchema } from "../../domain/python/pythonPyc.js";
|
||||
import { pythonPycLimitsSchema } from "../../domain/python/pythonPyc.js";
|
||||
import { createHash } from "node:crypto";
|
||||
import { open, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -209,7 +209,7 @@ it.each([
|
||||
bytes: pyc.length,
|
||||
sha256: createHash("sha256").update(pyc).digest("hex"),
|
||||
},
|
||||
inspectPythonPycInputSchema.parse({ path: "/fixtures/module.pyc" }),
|
||||
pythonPycLimitsSchema.parse({}),
|
||||
);
|
||||
expect(standalone.header.python_version).toBe(version);
|
||||
const image = pyzArchive({
|
||||
|
||||
@@ -356,7 +356,7 @@ export const registerCoreNativeCommands = (
|
||||
cli.command(CLI_COMMANDS.inspectNativeDataType, {
|
||||
description: "Inspect one recovered database type or typed data object",
|
||||
args: z.object({
|
||||
path: z.string().describe("Local native executable or app path"),
|
||||
path: localPathStringSchema.describe("Local native executable or app path"),
|
||||
}),
|
||||
options: z.object({
|
||||
type: z.string().optional().describe("Exact database type category path"),
|
||||
|
||||
@@ -26,7 +26,7 @@ export const registerDecompileDirectoryCommand = (
|
||||
description:
|
||||
"Write every function's pseudocode into a directory and return a manifest",
|
||||
args: z.object({
|
||||
path: z.string().describe("App or program path"),
|
||||
path: localPathStringSchema.describe("App or program path"),
|
||||
}),
|
||||
options: z.object({
|
||||
output: localPathStringSchema.describe(
|
||||
|
||||
@@ -8,6 +8,7 @@ import { logCliCommand } from "../cliLogging.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { localPathStringSchema } from "../domain/localPath.js";
|
||||
import { nativeCallObservationInputSchema } from "../domain/native/nativeCallObservation.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { CliInstance } from "./types.js";
|
||||
@@ -24,7 +25,7 @@ export const registerNativeCallCommands = (
|
||||
description:
|
||||
"Launch a Mach-O under LLDB and record entries into selected functions or Objective-C methods",
|
||||
args: z.object({
|
||||
path: z.string().describe("Mach-O or app path"),
|
||||
path: localPathStringSchema.describe("Mach-O or app path"),
|
||||
inputJson: z
|
||||
.string()
|
||||
.describe(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
import { graphqlContractViewRequestSchema } from "./graphqlContractQuery.js";
|
||||
import {
|
||||
API_CONTRACT_MAX_FILE_BYTES,
|
||||
@@ -59,7 +59,7 @@ export const apiContractViewSelectionSchema = z.discriminatedUnion("kind", [
|
||||
graphqlContractViewRequestSchema,
|
||||
]);
|
||||
const commonInput = {
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute path to a local API contract document",
|
||||
),
|
||||
max_file_bytes: apiContractFileBytesSchema,
|
||||
@@ -79,7 +79,7 @@ export const inspectApiContractInputSchema = z
|
||||
z.strictObject({
|
||||
...commonInput,
|
||||
format: z.literal("graphql-sdl"),
|
||||
operations_path: localPathStringSchema
|
||||
operations_path: absoluteLocalPathStringSchema
|
||||
.optional()
|
||||
.describe(
|
||||
"Optional absolute path to one local GraphQL operation/fragment document; parsed without execution",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
const uint = z.number().int().min(0).max(0xffffffff);
|
||||
export const MAX_DEVICETREE_FILE_BYTES = 16777216;
|
||||
export const inspectDevicetreeInputSchema = z.strictObject({
|
||||
path: localPathStringSchema,
|
||||
path: absoluteLocalPathStringSchema,
|
||||
max_file_bytes: z
|
||||
.number()
|
||||
.int()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
traceDiagnosticSchema,
|
||||
traceEventSchema,
|
||||
@@ -56,7 +56,7 @@ export const playwrightTraceViewRequestSchema = z.discriminatedUnion(
|
||||
],
|
||||
);
|
||||
export const inspectPlaywrightTraceInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute path to an offline Playwright v10 trace ZIP",
|
||||
),
|
||||
detail: z.enum(["summary", "complete"]).default("summary"),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
|
||||
export const PYTHON_PYC_HEADER_BYTES = 16;
|
||||
export const PYTHON_PYC_HASH_BASED = 0x1;
|
||||
@@ -70,10 +73,7 @@ export const PYTHON_PYC_LIMIT_MAXIMA = {
|
||||
max_string_bytes: 8 * 1024 * 1024,
|
||||
} as const;
|
||||
|
||||
export const inspectPythonPycInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
"Absolute filesystem path to the selected CPython .pyc file",
|
||||
),
|
||||
export const pythonPycLimitsSchema = z.strictObject({
|
||||
max_file_bytes: z
|
||||
.number()
|
||||
.int()
|
||||
@@ -112,6 +112,13 @@ export const inspectPythonPycInputSchema = z.strictObject({
|
||||
),
|
||||
});
|
||||
|
||||
export type PythonPycLimits = z.infer<typeof pythonPycLimitsSchema>;
|
||||
export const inspectPythonPycInputSchema = pythonPycLimitsSchema.extend({
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to the selected CPython .pyc file",
|
||||
),
|
||||
});
|
||||
|
||||
export const PYTHON_PYC_CONST_PREVIEW_LIMIT = 80;
|
||||
/** Integers up to 256 bits fit the 80-character preview in decimal. */
|
||||
export const PYTHON_PYC_INT_PREVIEW_BITS = 256;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
PYTHON_PYC_HEADER_BYTES,
|
||||
PYTHON_PYC_INT_PREVIEW_BITS,
|
||||
pythonPycMarshalProfile,
|
||||
type InspectPythonPycInput,
|
||||
type PythonPycLimits,
|
||||
type PythonPyc,
|
||||
type PythonPycCodeLayout,
|
||||
type PythonPycCodeObject,
|
||||
@@ -202,7 +202,7 @@ const sha256 = (bytes: Buffer): string => {
|
||||
export const parsePythonPyc = (
|
||||
bytes: Buffer,
|
||||
artifact: { path: string; sha256: string; bytes: number },
|
||||
input: InspectPythonPycInput,
|
||||
input: PythonPycLimits,
|
||||
): PythonPyc => {
|
||||
if (bytes.length !== artifact.bytes)
|
||||
malformed("Selected artifact length disagrees with the retained bytes.");
|
||||
@@ -351,7 +351,7 @@ class MarshalReader {
|
||||
readonly bytes: Buffer,
|
||||
readonly layout: PythonPycCodeLayout,
|
||||
readonly magic: number,
|
||||
readonly input: InspectPythonPycInput,
|
||||
readonly input: PythonPycLimits,
|
||||
readonly rejectNegativeCodeFields: boolean,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const fileCommands = [
|
||||
"inspect-rust-binary",
|
||||
"inspect-gpu-code-objects",
|
||||
"inspect-il2cpp-metadata",
|
||||
"inspect-native-data-type",
|
||||
] as const;
|
||||
|
||||
cliTest(
|
||||
@@ -38,6 +39,8 @@ cliTest(
|
||||
arguments: [command, ""],
|
||||
field: "path",
|
||||
})),
|
||||
{ arguments: ["decompile-all", "", "--output", "out"], field: "path" },
|
||||
{ arguments: ["observe-native-calls", "", "{}"], field: "path" },
|
||||
{ arguments: ["inspect-evm-interface", "", "hex"], field: "path" },
|
||||
{
|
||||
arguments: ["inspect-web-network-capture", "", "har"],
|
||||
|
||||
@@ -6,6 +6,9 @@ import { expect, it } from "vitest";
|
||||
import { JAVASCRIPT_RECOVERY_TOOL_CONTRACTS } from "../../../src/contracts/javascript/javascriptRecoveryToolContracts.js";
|
||||
import { BINARY_DIAGNOSTICS_TOOL_CONTRACTS } from "../../../src/contracts/native/binaryDiagnosticsToolContracts.js";
|
||||
import { WEB_SCRIPT_TOOL_CONTRACTS } from "../../../src/contracts/webScriptToolContracts.js";
|
||||
import { API_CONTRACT_TOOL_CONTRACTS } from "../../../src/contracts/apiContractToolContracts.js";
|
||||
import { PLAYWRIGHT_TRACE_TOOL_CONTRACTS } from "../../../src/contracts/playwrightTraceToolContracts.js";
|
||||
import { PYTHON_PYC_TOOL_CONTRACTS } from "../../../src/contracts/python/pythonPycToolContracts.js";
|
||||
import { RequestTrackingMcpServer } from "../../../src/server/RequestTrackingMcpServer.js";
|
||||
import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js";
|
||||
|
||||
@@ -13,6 +16,9 @@ const CONTRACTS = [
|
||||
...BINARY_DIAGNOSTICS_TOOL_CONTRACTS,
|
||||
...WEB_SCRIPT_TOOL_CONTRACTS,
|
||||
...JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
|
||||
...API_CONTRACT_TOOL_CONTRACTS,
|
||||
...PLAYWRIGHT_TRACE_TOOL_CONTRACTS,
|
||||
...PYTHON_PYC_TOOL_CONTRACTS,
|
||||
];
|
||||
|
||||
const PATH_FIELDS = {
|
||||
@@ -20,6 +26,10 @@ const PATH_FIELDS = {
|
||||
inspect_elf_notes: ["path"],
|
||||
inspect_kernel_module: ["path"],
|
||||
inspect_byte_entropy: ["path"],
|
||||
inspect_api_contract: ["path", "operations_path"],
|
||||
inspect_devicetree: ["path"],
|
||||
inspect_playwright_trace: ["path"],
|
||||
inspect_python_pyc: ["path"],
|
||||
export_web_scripts: ["capture_path", "output_directory"],
|
||||
recover_javascript_sources: ["path", "output_directory", "additional_paths"],
|
||||
} as const;
|
||||
@@ -37,6 +47,12 @@ const contractFor = (name: keyof typeof PATH_FIELDS) => {
|
||||
const validInput = (
|
||||
name: keyof typeof PATH_FIELDS,
|
||||
): Record<string, unknown> => {
|
||||
if (name === "inspect_api_contract")
|
||||
return {
|
||||
format: "graphql-sdl",
|
||||
path: absoluteFile,
|
||||
operations_path: absoluteFile,
|
||||
};
|
||||
if (name === "export_web_scripts")
|
||||
return {
|
||||
capture_path: absoluteFile,
|
||||
@@ -108,6 +124,11 @@ it("rejects one relative path field at a time on tools/list", async () => {
|
||||
"inspect_elf_notes.path",
|
||||
"inspect_kernel_module.path",
|
||||
"inspect_byte_entropy.path",
|
||||
"inspect_api_contract.path",
|
||||
"inspect_api_contract.operations_path",
|
||||
"inspect_devicetree.path",
|
||||
"inspect_playwright_trace.path",
|
||||
"inspect_python_pyc.path",
|
||||
"export_web_scripts.capture_path",
|
||||
"export_web_scripts.output_directory",
|
||||
"recover_javascript_sources.path",
|
||||
|
||||
Reference in new issue
Block a user