mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(native): preserve literal environment map members
This commit is contained in:
1 parent
0f5ece1d58
commit
b413332bd8
3 files changed
+68
-10
No files matched your search
@@ -730,7 +730,7 @@ cancellation Evidence, even if the target has already exited.
|
||||
while excess bytes are drained and counted without growing capture files).
|
||||
Each stream reports `complete`; when draining could not finish, its byte
|
||||
count is an observed lower bound and truncation is explicit;
|
||||
- caller-selected arguments, working directory, and environment overrides;
|
||||
- caller-selected arguments, working directory, and environment overrides, including literal JSON environment names such as `__proto__`;
|
||||
- `selected_file_sha256` and LLDB module identity observations. These checks do
|
||||
not pin the executable against concurrent mutation; `loaded_image_sha256`
|
||||
remains unknown;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { jsonObjectSchema } from "../jsonValue.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
|
||||
const hexSchema = z.string().regex(/^0x[0-9a-f]+$/u);
|
||||
@@ -58,6 +59,37 @@ export const nativeCallBreakpointSchema = z.discriminatedUnion("kind", [
|
||||
}),
|
||||
]);
|
||||
|
||||
const environmentNameSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[^=\x00]+$/u, "Environment names cannot contain '=' or NUL");
|
||||
const environmentValueSchema = z
|
||||
.string()
|
||||
.regex(/^[^\x00]*$/u, "Environment values cannot contain NUL");
|
||||
const environmentRecordProjection = z.record(
|
||||
environmentNameSchema,
|
||||
environmentValueSchema,
|
||||
);
|
||||
|
||||
// z.record skips __proto__. Own the JSON map before validating entries so every
|
||||
// selected environment name survives input parsing, readback and partial Evidence.
|
||||
const nativeCallEnvironmentSchema = jsonObjectSchema
|
||||
.superRefine((value, context) => {
|
||||
for (const [name, entry] of Object.entries(value)) {
|
||||
for (const parsed of [
|
||||
environmentNameSchema.safeParse(name),
|
||||
environmentValueSchema.safeParse(entry),
|
||||
]) {
|
||||
if (!parsed.success)
|
||||
for (const issue of parsed.error.issues)
|
||||
context.addIssue({ ...issue, path: [name, ...issue.path] });
|
||||
}
|
||||
}
|
||||
})
|
||||
.transform((value) => value as Record<string, string>);
|
||||
nativeCallEnvironmentSchema._zod.toJSONSchema = () =>
|
||||
z.toJSONSchema(environmentRecordProjection, { io: "input" });
|
||||
|
||||
/**
|
||||
* Launch the active Mach-O as an owned process under LLDB and observe calls.
|
||||
* Duration and event bounds keep runtime observation finite.
|
||||
@@ -65,14 +97,7 @@ export const nativeCallBreakpointSchema = z.discriminatedUnion("kind", [
|
||||
export const nativeCallObservationInputSchema = z.strictObject({
|
||||
breakpoints: z.array(nativeCallBreakpointSchema).min(1),
|
||||
arguments: z.array(nativeString).default([]),
|
||||
environment: z
|
||||
.record(
|
||||
z
|
||||
.string()
|
||||
.min(1)
|
||||
.regex(/^[^=\x00]+$/u, "Environment names cannot contain '=' or NUL"),
|
||||
z.string().regex(/^[^\x00]*$/u, "Environment values cannot contain NUL"),
|
||||
)
|
||||
environment: nativeCallEnvironmentSchema
|
||||
.default({})
|
||||
.describe("Overrides on top of the environment REA runs with."),
|
||||
working_directory: localPathStringSchema.optional(),
|
||||
@@ -152,7 +177,7 @@ export const nativeCallObservationResultSchema = z.strictObject({
|
||||
sha256: z.string().regex(/^[0-9a-f]{64}$/u),
|
||||
architecture: z.string(),
|
||||
arguments: z.array(z.string()),
|
||||
environment: z.record(z.string(), z.string()),
|
||||
environment: nativeCallEnvironmentSchema,
|
||||
working_directory: z.string().nullable(),
|
||||
launch_identity: z.strictObject({
|
||||
/** A pathname hash does not verify the bytes mapped into the process. */
|
||||
|
||||
@@ -32,6 +32,12 @@ const tracer: NativeCallTracer & { calls: number } = {
|
||||
calls: 0,
|
||||
trace(request) {
|
||||
this.calls++;
|
||||
expect(request.input.environment).toEqual(
|
||||
JSON.parse(
|
||||
'{"__proto__":"selected literal value","constructor":"ordinary value"}',
|
||||
),
|
||||
);
|
||||
expect(Object.hasOwn(request.input.environment, "__proto__")).toBe(true);
|
||||
return Promise.resolve(
|
||||
ok({
|
||||
debugger: {
|
||||
@@ -113,6 +119,9 @@ it("routes observe_native_calls through MCP with schema-checked input and output
|
||||
arguments: {
|
||||
breakpoints: [{ kind: "function", name: "open" }],
|
||||
duration_ms: 1000,
|
||||
environment: JSON.parse(
|
||||
'{"__proto__":"selected literal value","constructor":"ordinary value"}',
|
||||
),
|
||||
},
|
||||
});
|
||||
expect(called.isError, JSON.stringify(called.structuredContent)).not.toBe(
|
||||
@@ -124,11 +133,35 @@ it("routes observe_native_calls through MCP with schema-checked input and output
|
||||
.parse(called.structuredContent).normalized_result,
|
||||
);
|
||||
expect(result.process).toMatchObject({ pid: 99, outcome: "exited" });
|
||||
expect(result.target.environment).toEqual(
|
||||
JSON.parse(
|
||||
'{"__proto__":"selected literal value","constructor":"ordinary value"}',
|
||||
),
|
||||
);
|
||||
expect(Object.hasOwn(result.target.environment, "__proto__")).toBe(true);
|
||||
expect(result.breakpoints[0]?.request).toEqual({
|
||||
kind: "function",
|
||||
name: "open",
|
||||
module: null,
|
||||
});
|
||||
const invalidEnvironment = await client.callTool({
|
||||
name: "observe_native_calls",
|
||||
arguments: {
|
||||
breakpoints: [{ kind: "function", name: "open" }],
|
||||
environment: JSON.parse('{"__proto__":7}'),
|
||||
},
|
||||
});
|
||||
expect(parseMcpToolError(invalidEnvironment).error).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
issues: [
|
||||
expect.objectContaining({
|
||||
path: ["environment", "__proto__"],
|
||||
reason: "invalid_type",
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
const rejected = await client.callTool({
|
||||
name: "observe_native_calls",
|
||||
arguments: {
|
||||
|
||||
Reference in new issue
Block a user