mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix: reject PTY environment names the backend cannot preserve
This commit is contained in:
@@ -12,6 +12,11 @@ sandbox. Filesystem observation paths select what REA snapshots; they do not
|
||||
restrict what the process can read or write. The inherited environment is not
|
||||
recorded, so its influence may remain unknown.
|
||||
|
||||
The installed PTY backend cannot preserve an explicit environment name
|
||||
`__proto__`. REA rejects that override before launch; use `/usr/bin/env` with
|
||||
`__proto__=VALUE` as an argument when that literal variable is required. The
|
||||
reserved `REA_PROCESS_RUN_ID` name also remains unavailable to scenario overrides.
|
||||
|
||||
## Host support
|
||||
|
||||
Capturing a new scenario currently requires Linux or macOS and a working
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
|
||||
import { SESSION_TOOL_CONTRACTS } from "../../contracts/sessionToolContracts.js";
|
||||
import {
|
||||
@@ -12,6 +14,23 @@ import {
|
||||
|
||||
const baseScenario = { executable: "/usr/bin/true" };
|
||||
|
||||
it("advertises the same unsupported PTY environment name as runtime admission", () => {
|
||||
const schema = z.toJSONSchema(processScenarioSchema.shape.environment, {
|
||||
io: "input",
|
||||
});
|
||||
const ajv = new Ajv2020({ strict: false });
|
||||
const validate = ajv.compile(schema);
|
||||
expect(validate(Object.fromEntries([["__proto__", "selected"]]))).toBe(false);
|
||||
expect(
|
||||
validate({
|
||||
constructor: "ordinary",
|
||||
prototype: "ordinary",
|
||||
NORMAL_CAPTURE_KEY: "control",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(validate({ REA_PROCESS_RUN_ID: "reserved" })).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults the finalization interval to zero without changing committed identity", () => {
|
||||
const scenario = parseProcessScenario(baseScenario);
|
||||
|
||||
@@ -237,3 +256,26 @@ it("identifies each event scheduled after the scenario timeout", () => {
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it.each(["selected-value", 7])(
|
||||
"rejects a selected __proto__ PTY environment value instead of omitting %j",
|
||||
(value) => {
|
||||
const environment = Object.fromEntries([
|
||||
["__proto__", value],
|
||||
["NORMAL_CAPTURE_KEY", "control"],
|
||||
]);
|
||||
const parsed = processScenarioSchema.safeParse({
|
||||
...baseScenario,
|
||||
environment,
|
||||
});
|
||||
expect(parsed.success).toBe(false);
|
||||
if (parsed.success)
|
||||
throw new Error("Expected the unsupported PTY name to be rejected");
|
||||
expect(parsed.error.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: ["environment", "__proto__"],
|
||||
message: expect.stringContaining("PTY backend cannot preserve"),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -45,12 +45,43 @@ const environmentName = z
|
||||
? `${reservedRunIdEnvironmentName} is reserved by the process adapter`
|
||||
: "Environment names cannot contain '=' or NUL",
|
||||
});
|
||||
const unsupportedPtyEnvironmentName = "__proto__";
|
||||
const ptyEnvironmentName = environmentName.regex(
|
||||
environmentNamePattern(unsupportedPtyEnvironmentName),
|
||||
{
|
||||
error:
|
||||
"The installed PTY backend cannot preserve the __proto__ environment name; pass it through /usr/bin/env instead.",
|
||||
},
|
||||
);
|
||||
const childProcessString = z
|
||||
.string()
|
||||
.regex(
|
||||
/^[^\x00]*$/u,
|
||||
"Values passed to operating-system APIs cannot contain NUL",
|
||||
);
|
||||
const environmentRecordSchema = z.record(
|
||||
ptyEnvironmentName,
|
||||
childProcessString,
|
||||
);
|
||||
// z.record intentionally skips this key, so validate its presence before parsing.
|
||||
const processEnvironmentSchema = z
|
||||
.unknown()
|
||||
.superRefine((value, context) => {
|
||||
if (
|
||||
value !== null &&
|
||||
typeof value === "object" &&
|
||||
Object.hasOwn(value, unsupportedPtyEnvironmentName)
|
||||
)
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: [unsupportedPtyEnvironmentName],
|
||||
message:
|
||||
"The installed PTY backend cannot preserve the __proto__ environment name; pass it through /usr/bin/env instead.",
|
||||
});
|
||||
})
|
||||
.pipe(environmentRecordSchema);
|
||||
processEnvironmentSchema._zod.toJSONSchema = () =>
|
||||
z.toJSONSchema(environmentRecordSchema, { io: "input" });
|
||||
|
||||
const fitsProcessTerminalCellBudget = (
|
||||
columns: number,
|
||||
@@ -153,7 +184,7 @@ export const processScenarioSchema = z
|
||||
executable: childProcessString.min(1),
|
||||
arguments: z.array(childProcessString).default([]),
|
||||
working_directory: childProcessString.default("."),
|
||||
environment: z.record(environmentName, childProcessString).default({}),
|
||||
environment: processEnvironmentSchema.default({}),
|
||||
filesystem_observation_paths: z
|
||||
.array(childProcessString.min(1))
|
||||
.default([]),
|
||||
|
||||
Reference in New Issue
Block a user