fix(evm): explain limiter configuration and access failures (#8669)

This commit is contained in:
ShuaixinHuang authored and GitHub committed 2026-11-03 09:38:01 +08:00
1 parent 6442ea02cb
commit b7911b2d30
4 files changed
+121 -31

No files matched your search

+9
View File
@@ -28,6 +28,15 @@ The host must already provide util-linux `prlimit`, normally `/usr/bin/prlimit`;
no system tools and changes no user configuration. [Upstream provenance and
license](https://github.com/morluto/rea/blob/main/third_party/evmole/README.md) are packaged.
Limiter discovery validates the selected path without launching it. A relative
path or directory is reported as `not_configured`, a missing file as
`executable_missing`, and denied access or another filesystem-probe failure as
`runtime_unavailable`. Recovery names the selected path and
`REA_EVM_PRLIMIT_COMMAND`; denied access asks for execute permission and
filesystem access. The original admission or system code stays in diagnostics.
An available filesystem probe does not verify the selected tool's version or
util-linux compatibility, and REA does not substitute another command.
## Evidence semantics
- `encoding` is required: raw bytes or UTF-8 hex, with optional `0x` prefix and
+29 -4
View File
@@ -1,4 +1,5 @@
import { EvmAdmissionError } from "./EvmAdmissionError.js";
import type { ProviderRejectionCode } from "../domain/providerSelection.js";
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { safeParseUtf8Json } from "../domain/safeJson.js";
@@ -115,6 +116,7 @@ type EvmLimiterSelection = {
readonly diagnostics: Readonly<Record<string, JsonValue>>;
};
type EvmLimiterUnavailable = EvmLimiterSelection & {
readonly code: ProviderRejectionCode;
readonly reason: string;
readonly cause: unknown;
};
@@ -159,16 +161,39 @@ const selectEvmLimiter = async (
if (cause instanceof EvmAdmissionError)
return err({
path,
code: "not_configured",
diagnostics: {
...diagnostics,
code: cause.code,
...cause.detail,
},
reason: cause.code,
reason: `Set REA_EVM_PRLIMIT_COMMAND to an absolute executable regular file for caller-supplied util-linux prlimit. The selected path ${path} ${cause.code === "prlimit_not_absolute" ? "is not absolute" : "is not a regular file"}.`,
cause,
});
const reason = `Offline EVM interface inspection requires caller-supplied util-linux prlimit: ${path}; ${cause instanceof Error ? cause.message : String(cause)}`;
return err({ path, diagnostics, reason, cause });
const systemCode =
cause instanceof Error &&
"code" in cause &&
typeof cause.code === "string"
? cause.code
: undefined;
const permissionDenied = systemCode === "EACCES" || systemCode === "EPERM";
const missing = systemCode === "ENOENT" || systemCode === "ENOTDIR";
const recovery = permissionDenied
? "Grant execute permission and filesystem access to the selected file, or set REA_EVM_PRLIMIT_COMMAND to an accessible absolute executable regular file for util-linux prlimit."
: missing
? "Set REA_EVM_PRLIMIT_COMMAND to an existing absolute executable regular file for util-linux prlimit."
: "Check the selected file and filesystem access, or set REA_EVM_PRLIMIT_COMMAND to an accessible absolute executable regular file for util-linux prlimit.";
const reason = `Offline EVM interface inspection requires caller-supplied util-linux prlimit: ${path}; ${cause instanceof Error ? cause.message : String(cause)}. ${recovery}`;
return err({
path,
code: missing ? "executable_missing" : "runtime_unavailable",
diagnostics: {
...diagnostics,
...(systemCode === undefined ? {} : { system_code: systemCode }),
},
reason,
cause,
});
}
};
@@ -193,7 +218,7 @@ export const evmLimiterAvailability = async (
}
: {
status: "unavailable",
code: "executable_missing",
code: selected.error.code,
reason: selected.error.reason,
// The limiter owner does not yet author a separate repair (#8508).
remediation: selected.error.reason,
@@ -1,4 +1,7 @@
import { expect, it } from "vitest";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { withVerifierCleanup } from "../../../scripts/lib/verifier-cleanup.mjs";
import { buildCapabilityInventory } from "../../../src/application/CapabilityInventory.js";
@@ -41,33 +44,43 @@ it("probes the default limiter with executable and regular-file checks", async (
]);
});
it("uses the exact absolute override and reports a failed executable check", async () => {
const limiter = "/fixture/util-linux/prlimit";
const availability = await evmLimiterAvailability(
{ REA_EVM_PRLIMIT_COMMAND: limiter },
{
...linux,
fs: {
access: async (path) => {
expect(path).toBe(limiter);
throw Object.assign(new Error("permission denied"), {
code: "EACCES",
});
},
stat: async () => {
throw new Error("stat must not run after access failure");
it.each(["EACCES", "EPERM"])(
"uses the exact absolute override and reports denied access: %s",
async (code) => {
const limiter = "/fixture/util-linux/prlimit";
const availability = await evmLimiterAvailability(
{ REA_EVM_PRLIMIT_COMMAND: limiter },
{
...linux,
fs: {
access: async (path) => {
expect(path).toBe(limiter);
throw Object.assign(new Error("permission denied"), {
code,
});
},
stat: async () => {
throw new Error("stat must not run after access failure");
},
},
},
},
);
);
expect(availability).toMatchObject({
status: "unavailable",
code: "executable_missing",
reason: expect.stringContaining(limiter),
diagnostics: { limiter_path: limiter },
});
});
expect(availability).toMatchObject({
status: "unavailable",
code: "runtime_unavailable",
reason: expect.stringContaining(limiter),
diagnostics: { limiter_path: limiter, system_code: code },
});
if (availability === undefined) throw new Error("Expected limiter probe");
const entry = buildCapabilityInventory(status, {
...basePolicy,
evmLimiterAvailability: availability,
}).find(({ name }) => name === "inspect_evm_interface");
expect(entry?.remediation).toContain("REA_EVM_PRLIMIT_COMMAND");
expect(entry?.remediation).toContain("execute permission");
},
);
it("rejects a relative override and non-file without probing an alternate command", async () => {
const relative = await evmLimiterAvailability(
@@ -84,7 +97,8 @@ it("rejects a relative override and non-file without probing an alternate comman
);
expect(relative).toMatchObject({
status: "unavailable",
reason: "prlimit_not_absolute",
code: "not_configured",
reason: expect.stringContaining("absolute executable regular file"),
diagnostics: {
limiter_path: "prlimit",
code: "prlimit_not_absolute",
@@ -104,11 +118,53 @@ it("rejects a relative override and non-file without probing an alternate comman
);
expect(directory).toMatchObject({
status: "unavailable",
reason: "prlimit_not_file",
code: "not_configured",
reason: expect.stringContaining("absolute executable regular file"),
diagnostics: expect.objectContaining({ code: "prlimit_not_file" }),
});
});
it("delivers selected-path recovery from real filesystem probes to capability output", async () => {
const root = await createTestTempDirectory("rea-evm-limiter-recovery-");
const executable = join(root, "caller-selected-limiter");
await writeFile(executable, "not executed by the readiness probe", {
mode: 0o700,
});
for (const [path, expectedCode, expectedReason] of [
["prlimit", "not_configured", "absolute executable regular file"],
[root, "not_configured", "absolute executable regular file"],
[
join(root, "missing-limiter"),
"executable_missing",
"REA_EVM_PRLIMIT_COMMAND",
],
[executable, null, null],
] as const) {
const availability = await evmLimiterAvailability(
{ REA_EVM_PRLIMIT_COMMAND: path },
linux,
);
expect(availability?.code).toBe(expectedCode);
expect(availability?.diagnostics.limiter_path).toBe(path);
if (availability === undefined) throw new Error("Expected limiter probe");
const entry = buildCapabilityInventory(status, {
...basePolicy,
evmLimiterAvailability: availability,
}).find(({ name }) => name === "inspect_evm_interface");
if (expectedReason === null) {
expect(entry).toMatchObject({ available: true, remediation: null });
} else {
expect(entry).toMatchObject({
available: false,
reason: "provider_missing",
});
expect(entry?.remediation).toContain(expectedReason);
expect(entry?.remediation).toContain(path);
expect(entry?.remediation).toContain("util-linux prlimit");
}
}
});
it("rechecks on each observation so inventory follows the selected executable's current state", async () => {
let accessible = true;
const fs = {
@@ -970,7 +970,7 @@ it.runIf(!unsupportedHost).each(["directory", "missing-interpreter"])(
});
if (scenario === "directory")
expect(result.error).toMatchObject({
reason: "prlimit_not_file",
reason: expect.stringContaining("absolute executable regular file"),
diagnostics: {
limiter_path: limiter,
code: "prlimit_not_file",