fix: reject directory paths for the Wakaru limiter and mitmdump

`REA_JAVASCRIPT_PRLIMIT_COMMAND` and `REA_MITMDUMP_COMMAND` were admitted
after an access check alone, which a searchable directory passes. Admission
then succeeded, and the failure surfaced later as a launch-time adapter
error instead of the capability refusal that names the configured path.

Require a regular file, as #751 did for the firmware engine and limiter.
Linux-only tests mirror that change; both checks only run on Linux hosts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Fazmin
2026-10-09 23:52:38 -04:00
committed by morluto
parent bd3a9bbbaf
commit a67dbf6a48
4 changed files with 91 additions and 2 deletions
@@ -1,4 +1,4 @@
import { access } from "node:fs/promises";
import { access, stat } from "node:fs/promises";
import { constants } from "node:fs";
import { isAbsolute } from "node:path";
import { fileURLToPath } from "node:url";
@@ -32,6 +32,8 @@ export class MitmproxyCaptureAdapter implements HistoricalCaptureFormatAdapter {
);
try {
await access(command, constants.R_OK | constants.X_OK);
if (!(await stat(command)).isFile())
throw new TypeError("Configured path is not a regular file");
} catch (cause: unknown) {
const reason = `Configured mitmdump executable is unavailable: ${command}.`;
throw new AnalysisCapabilityUnavailableError(
+3 -1
View File
@@ -1,6 +1,6 @@
import { randomUUID } from "node:crypto";
import { constants } from "node:fs";
import { access, realpath } from "node:fs/promises";
import { access, realpath, stat } from "node:fs/promises";
import { isAbsolute } from "node:path";
import {
AnalysisCancelledError,
@@ -87,6 +87,8 @@ export const resolveWakaruCommand = async (
if (!isAbsolute(limiter))
throw new TypeError("REA_JAVASCRIPT_PRLIMIT_COMMAND must be absolute");
await access(limiter, constants.X_OK);
if (!(await stat(limiter)).isFile())
throw new TypeError(`Resource limiter is not a regular file: ${limiter}`);
return { command, limiter, ...fingerprint };
} catch (cause: unknown) {
const reason = `Configured Wakaru or util-linux prlimit is unavailable: ${recoveryFailureMessage(cause)}`;
@@ -2,8 +2,10 @@ import { PrivateRuntimeRoot } from "../../../src/process/PrivateRuntimeRoot.js";
import {
access,
chmod,
mkdir,
mkdtemp,
rm,
symlink,
truncate,
writeFile,
} from "node:fs/promises";
@@ -11,6 +13,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { expect, it, onTestFinished } from "vitest";
import { HistoricalCaptureDecoder } from "../../../src/browser/history/HistoricalCaptureDecoder.js";
import { MitmproxyCaptureAdapter } from "../../../src/browser/history/MitmproxyCaptureAdapter.js";
import { HAR_CAPTURE_PROVIDER_IDENTITY } from "../../../src/browser/history/CaptureRelease.js";
import {
inspectWebNetworkCaptureInputSchema,
@@ -20,6 +23,41 @@ import { historicalHar } from "../../fixtures/historicalHar.js";
import { decodeHarCapture } from "../../../src/browser/history/HarCapture.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
const linux = it.skipIf(process.platform !== "linux");
linux("refuses a directory configured as the mitmdump executable", async () => {
const root = await mkdtemp(join(tmpdir(), "rea-mitmdump-command-"));
onTestFinished(() => rm(root, { recursive: true, force: true }));
const command = join(root, "mitmdump");
await mkdir(command);
await expect(
new MitmproxyCaptureAdapter({ REA_MITMDUMP_COMMAND: command }).command(
join(root, "request.json"),
join(root, "runtime"),
),
).rejects.toMatchObject({
_tag: "AnalysisCapabilityUnavailableError",
reason: expect.stringContaining(command),
});
});
linux("allows a symlink to a regular mitmdump executable", async () => {
const root = await mkdtemp(join(tmpdir(), "rea-mitmdump-command-symlink-"));
onTestFinished(() => rm(root, { recursive: true, force: true }));
const command = join(root, "mitmdump");
const target = join(root, "mitmdump-bin");
await writeFile(target, "#!/bin/sh\n", { mode: 0o700 });
await symlink(target, command);
await expect(
new MitmproxyCaptureAdapter({ REA_MITMDUMP_COMMAND: command }).command(
join(root, "request.json"),
join(root, "runtime"),
),
).resolves.toMatchObject({ command });
});
it.each([true, false])(
"binds decoder replies to the complete selected provider identity (matching name: %s)",
async (matching) => {
@@ -0,0 +1,47 @@
import { mkdir, symlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { resolveWakaruCommand } from "../../../src/javascript/recovery/WakaruCommand.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const linuxX64 = it.skipIf(
process.platform !== "linux" || process.arch !== "x64",
);
linuxX64(
"refuses a directory configured as the Wakaru resource limiter",
async () => {
const root = await createTestTempDirectory("rea-wakaru-limiter-");
const command = join(root, "wakaru");
const limiter = join(root, "prlimit");
await writeFile(command, "#!/bin/sh\n", { mode: 0o700 });
await mkdir(limiter);
await expect(
resolveWakaruCommand({
REA_WAKARU_COMMAND: command,
REA_JAVASCRIPT_PRLIMIT_COMMAND: limiter,
}),
).rejects.toMatchObject({
_tag: "AnalysisCapabilityUnavailableError",
reason: expect.stringContaining(limiter),
});
},
);
linuxX64("allows a symlink to a regular Wakaru resource limiter", async () => {
const root = await createTestTempDirectory("rea-wakaru-limiter-symlink-");
const command = join(root, "wakaru");
const limiter = join(root, "prlimit");
const target = join(root, "prlimit-bin");
await writeFile(command, "#!/bin/sh\n", { mode: 0o700 });
await writeFile(target, "#!/bin/sh\n", { mode: 0o700 });
await symlink(target, limiter);
await expect(
resolveWakaruCommand({
REA_WAKARU_COMMAND: command,
REA_JAVASCRIPT_PRLIMIT_COMMAND: limiter,
}),
).resolves.toMatchObject({ limiter });
});